feat(change_detection): implement a change detector generator

This commit is contained in:
vsavkin 2015-01-21 12:05:52 -08:00
parent b78c1252e5
commit 850cf0fef4
31 changed files with 1347 additions and 694 deletions

View File

@ -8,6 +8,7 @@ import {
Parser, Parser,
ChangeDetector, ChangeDetector,
ProtoChangeDetector, ProtoChangeDetector,
DynamicProtoChangeDetector,
ChangeDispatcher, ChangeDispatcher,
} from 'change_detection/change_detection'; } from 'change_detection/change_detection';
@ -102,7 +103,7 @@ function setUpChangeDetection(iterations) {
var dispatcher = new DummyDispatcher(); var dispatcher = new DummyDispatcher();
var parser = new Parser(new Lexer()); var parser = new Parser(new Lexer());
var parentProto = new ProtoChangeDetector(); var parentProto = new DynamicProtoChangeDetector();
var parentCD = parentProto.instantiate(dispatcher, MapWrapper.create()); var parentCD = parentProto.instantiate(dispatcher, MapWrapper.create());
var astWithSource = [ var astWithSource = [
@ -119,7 +120,7 @@ function setUpChangeDetection(iterations) {
]; ];
function proto(i) { function proto(i) {
var pcd = new ProtoChangeDetector(); var pcd = new DynamicProtoChangeDetector();
pcd.addAst(astWithSource[i % 10].ast, "memo", i, false); pcd.addAst(astWithSource[i % 10].ast, "memo", i, false);
return pcd; return pcd;
} }

View File

@ -3,7 +3,7 @@ import {isBlank, Type} from 'facade/lang';
import {MapWrapper} from 'facade/collection'; import {MapWrapper} from 'facade/collection';
import {DirectiveMetadata} from 'core/compiler/directive_metadata'; import {DirectiveMetadata} from 'core/compiler/directive_metadata';
import {Parser, Lexer, ProtoRecordRange} from 'change_detection/change_detection'; import {Parser, Lexer, ProtoRecordRange, dynamicChangeDetection} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/compiler/compiler'; import {Compiler, CompilerCache} from 'core/compiler/compiler';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader'; import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
@ -79,7 +79,7 @@ export function main() {
setupReflector(); setupReflector();
var reader = new DirectiveMetadataReader(); var reader = new DirectiveMetadataReader();
var cache = new CompilerCache(); var cache = new CompilerCache();
var compiler = new Compiler(null, reader, new Parser(new Lexer()), cache); var compiler = new Compiler(dynamicChangeDetection, null, reader, new Parser(new Lexer()), cache);
var annotatedComponent = reader.read(BenchmarkComponent); var annotatedComponent = reader.read(BenchmarkComponent);
var templateNoBindings = loadTemplate('templateNoBindings', count); var templateNoBindings = loadTemplate('templateNoBindings', count);

View File

@ -1,4 +1,5 @@
import {Parser, Lexer, ChangeDetector} from 'change_detection/change_detection'; import {Parser, Lexer, ChangeDetector, ChangeDetection, jitChangeDetection}
from 'change_detection/change_detection';
import {bootstrap, Component, Template, TemplateConfig, ViewPort, Compiler} from 'angular/angular'; import {bootstrap, Component, Template, TemplateConfig, ViewPort, Compiler} from 'angular/angular';
@ -38,11 +39,7 @@ function setupReflector() {
}, },
template: new TemplateConfig({ template: new TemplateConfig({
directives: [TreeComponent, NgIf], directives: [TreeComponent, NgIf],
inline: ` inline: `<span>{{data.value}}<span template='ng-if data.right != null'><tree [data]='data.right'></tree></span><span template='ng-if data.left != null'><tree [data]='data.left'></tree></span></span>`
<span> {{data.value}}
<span template='ng-if data.right != null'><tree [data]='data.right'></tree></span>
<span template='ng-if data.left != null'><tree [data]='data.left'></tree></span>
</span>`
}) })
})] })]
}); });
@ -59,8 +56,8 @@ function setupReflector() {
}); });
reflector.registerType(Compiler, { reflector.registerType(Compiler, {
'factory': (templateLoader, reader, parser, compilerCache) => new Compiler(templateLoader, reader, parser, compilerCache), 'factory': (cd, templateLoader, reader, parser, compilerCache) => new Compiler(cd, templateLoader, reader, parser, compilerCache),
'parameters': [[TemplateLoader], [DirectiveMetadataReader], [Parser], [CompilerCache]], 'parameters': [[ChangeDetection], [TemplateLoader], [DirectiveMetadataReader], [Parser], [CompilerCache]],
'annotations': [] 'annotations': []
}); });

View File

@ -0,0 +1,46 @@
import {List, ListWrapper} from 'facade/collection';
import {ChangeDetector} from './interfaces';
export class AbstractChangeDetector extends ChangeDetector {
children:List;
parent:ChangeDetector;
constructor() {
this.children = [];
}
addChild(cd:ChangeDetector) {
ListWrapper.push(this.children, cd);
cd.parent = this;
}
removeChild(cd:ChangeDetector) {
ListWrapper.remove(this.children, cd);
}
remove() {
this.parent.removeChild(this);
}
detectChanges() {
this._detectChanges(false);
}
checkNoChanges() {
this._detectChanges(true);
}
_detectChanges(throwOnChange:boolean) {
this.detectChangesInRecords(throwOnChange);
this._detectChangesInChildren(throwOnChange);
}
detectChangesInRecords(throwOnChange:boolean){}
_detectChangesInChildren(throwOnChange:boolean) {
var children = this.children;
for(var i = 0; i < children.length; ++i) {
children[i]._detectChanges(throwOnChange);
}
}
}

View File

@ -5,5 +5,26 @@ export {ContextWithVariableBindings} from './parser/context_with_variable_bindin
export {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions'; export {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions';
export {ChangeRecord, ChangeDispatcher, ChangeDetector} from './interfaces'; export {ChangeRecord, ChangeDispatcher, ChangeDetector} from './interfaces';
export {ProtoChangeDetector} from './proto_change_detector'; export {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector} from './proto_change_detector';
export {DynamicChangeDetector} from './dynamic_change_detector'; export {DynamicChangeDetector} from './dynamic_change_detector';
import {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector} from './proto_change_detector';
export class ChangeDetection {
createProtoChangeDetector(name:string){}
}
export class DynamicChangeDetection extends ChangeDetection {
createProtoChangeDetector(name:string):ProtoChangeDetector{
return new DynamicProtoChangeDetector();
}
}
export class JitChangeDetection extends ChangeDetection {
createProtoChangeDetector(name:string):ProtoChangeDetector{
return new JitProtoChangeDetector();
}
}
export var dynamicChangeDetection = new DynamicChangeDetection();
export var jitChangeDetection = new JitChangeDetection();

View File

@ -0,0 +1,10 @@
library change_detectoin.change_detection_jit_generator;
class ChangeDetectorJITGenerator {
ChangeDetectorJITGenerator(typeName, records) {
}
generate() {
throw "Not supported in Dart";
}
}

View File

@ -0,0 +1,362 @@
import {isPresent, isBlank, BaseException, Type} from 'facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/collection';
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
import {AbstractChangeDetector} from './abstract_change_detector';
import {ChangeDetectionUtil} 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_INVOKE_FORMATTER,
RECORD_TYPE_STRUCTURAL_CHECK,
RECORD_TYPE_INTERPOLATE,
ProtoChangeDetector
} from './proto_change_detector';
/**
* The code generator takes a list of proto records and creates a function/class
* that "emulates" what the developer would write by hand to implement the same
* kind of behaviour.
*
* For example: An expression `address.city` will result in the following class:
*
* var ChangeDetector0 = function ChangeDetector0(dispatcher, formatters, protos) {
* AbstractChangeDetector.call(this);
* this.dispatcher = dispatcher;
* this.formatters = formatters;
* this.protos = protos;
*
* this.context = null;
* this.address0 = null;
* this.city1 = null;
* }
* ChangeDetector0.prototype = Object.create(AbstractChangeDetector.prototype);
*
* ChangeDetector0.prototype.detectChangesInRecords = function(throwOnChange) {
* var address0;
* var city1;
* var change;
* var changes = [];
* var temp;
* var context = this.context;
*
* temp = ChangeDetectionUtil.findContext("address", context);
* if (temp instanceof ContextWithVariableBindings) {
* address0 = temp.get('address');
* } else {
* address0 = temp.address;
* }
*
* if (address0 !== this.address0) {
* this.address0 = address0;
* }
*
* city1 = address0.city;
* if (city1 !== this.city1) {
* changes.push(ChangeDetectionUtil.simpleChangeRecord(this.protos[1].bindingMemento, this.city1, city1));
* this.city1 = city1;
* }
*
* if (changes.length > 0) {
* if(throwOnChange) ChangeDetectionUtil.throwOnChange(this.protos[1], changes[0]);
* this.dispatcher.onRecordChange('address.city', changes);
* changes = [];
* }
* }
*
*
* ChangeDetector0.prototype.setContext = function(context) {
* this.context = context;
* }
*
* return ChangeDetector0;
*
*
* The only thing the generated class depends on is the super class AbstractChangeDetector.
*
* The implementation comprises two parts:
* * ChangeDetectorJITGenerator has the logic of how everything fits together.
* * template functions (e.g., constructorTemplate) define what code is generated.
*/
var ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
var UTIL = "ChangeDetectionUtil";
var DISPATCHER_ACCESSOR = "this.dispatcher";
var FORMATTERS_ACCESSOR = "this.formatters";
var PROTOS_ACCESSOR = "this.protos";
var CHANGE_LOCAL = "change";
var CHANGES_LOCAL = "changes";
var TEMP_LOCAL = "temp";
function typeTemplate(type:string, cons:string, detectChanges:string, setContext:string):string {
return `
${cons}
${detectChanges}
${setContext};
return function(dispatcher, formatters) {
return new ${type}(dispatcher, formatters, protos);
}
`;
}
function constructorTemplate(type:string, fieldsDefinitions:string):string {
return `
var ${type} = function ${type}(dispatcher, formatters, protos) {
${ABSTRACT_CHANGE_DETECTOR}.call(this);
${DISPATCHER_ACCESSOR} = dispatcher;
${FORMATTERS_ACCESSOR} = formatters;
${PROTOS_ACCESSOR} = protos;
${fieldsDefinitions}
}
${type}.prototype = Object.create(${ABSTRACT_CHANGE_DETECTOR}.prototype);
`;
}
function setContextTemplate(type:string):string {
return `
${type}.prototype.setContext = function(context) {
this.context = context;
}
`;
}
function detectChangesTemplate(type:string, body:string):string {
return `
${type}.prototype.detectChangesInRecords = function(throwOnChange) {
${body}
}
`;
}
function bodyTemplate(localDefinitions:string, records:string):string {
return `
${localDefinitions}
var ${TEMP_LOCAL};
var ${CHANGE_LOCAL};
var ${CHANGES_LOCAL} = [];
context = this.context;
${records}
`;
}
function notifyTemplate(index:number):string{
return `
if (${CHANGES_LOCAL}.length > 0) {
if(throwOnChange) ${UTIL}.throwOnChange(${PROTOS_ACCESSOR}[${index}], ${CHANGES_LOCAL}[0]);
${DISPATCHER_ACCESSOR}.onRecordChange(${PROTOS_ACCESSOR}[${index}].groupMemento, ${CHANGES_LOCAL});
${CHANGES_LOCAL} = [];
}
`;
}
function structuralCheckTemplate(selfIndex:number, field:string, context:string, notify:string):string{
return `
${CHANGE_LOCAL} = ${UTIL}.structuralCheck(${field}, ${context});
if (${CHANGE_LOCAL}) {
${CHANGES_LOCAL}.push(${UTIL}.changeRecord(${PROTOS_ACCESSOR}[${selfIndex}].bindingMemento, ${CHANGE_LOCAL}));
${field} = ${CHANGE_LOCAL}.currentValue;
}
${notify}
`;
}
function referenceCheckTemplate(assignment, newValue, oldValue, addRecord, notify) {
return `
${assignment}
if (${newValue} !== ${oldValue} || (${newValue} !== ${newValue}) && (${oldValue} !== ${oldValue})) {
${addRecord}
${oldValue} = ${newValue};
}
${notify}
`;
}
function assignmentTemplate(field:string, value:string) {
return `${field} = ${value};`;
}
function propertyReadTemplate(name:string, context:string, newValue:string) {
return `
${TEMP_LOCAL} = ${UTIL}.findContext("${name}", ${context});
if (${TEMP_LOCAL} instanceof ContextWithVariableBindings) {
${newValue} = ${TEMP_LOCAL}.get('${name}');
} else {
${newValue} = ${TEMP_LOCAL}.${name};
}
`;
}
function localDefinitionsTemplate(names:List):string {
return names.map((n) => `var ${n};`).join("\n");
}
function fieldDefinitionsTemplate(names:List):string {
return names.map((n) => `${n} = ${UTIL}.unitialized();`).join("\n");
}
function addSimpleChangeRecordTemplate(protoIndex:number, oldValue:string, newValue:string) {
return `${CHANGES_LOCAL}.push(${UTIL}.simpleChangeRecord(${PROTOS_ACCESSOR}[${protoIndex}].bindingMemento, ${oldValue}, ${newValue}));`;
}
export class ChangeDetectorJITGenerator {
typeName:string;
records:List<ProtoRecord>;
localNames:List<String>;
fieldNames:List<String>;
constructor(typeName:string, records:List<ProtoRecord>) {
this.typeName = typeName;
this.records = records;
this.localNames = this.getLocalNames(records);
this.fieldNames = this.getFieldNames(this.localNames);
}
getLocalNames(records:List<ProtoRecord>):List<String> {
var index = 0;
var names = records.map((r) => {
var sanitizedName = r.name.replace(new RegExp("\\W", "g"), '');
return `${sanitizedName}${index++}`
});
return ["context"].concat(names);
}
getFieldNames(localNames:List<String>):List<String> {
return localNames.map((n) => `this.${n}`);
}
generate():Function {
var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(), this.genSetContext());
return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'ContextWithVariableBindings', 'protos', text)(AbstractChangeDetector, ChangeDetectionUtil, ContextWithVariableBindings, this.records);
}
genConstructor():string {
return constructorTemplate(this.typeName, fieldDefinitionsTemplate(this.fieldNames));
}
genSetContext():string {
return setContextTemplate(this.typeName);
}
genDetectChanges():string {
var body = this.genBody();
return detectChangesTemplate(this.typeName, body);
}
genBody():string {
var rec = this.records.map((r) => this.genRecord(r)).join("\n");
return bodyTemplate(this.genLocalDefinitions(), rec);
}
genLocalDefinitions():string {
return localDefinitionsTemplate(this.localNames);
}
genRecord(r:ProtoRecord):string {
if (r.mode == RECORD_TYPE_STRUCTURAL_CHECK) {
return this.getStructuralCheck(r);
} else {
return this.genReferenceCheck(r);
}
}
getStructuralCheck(r:ProtoRecord):string {
var field = this.fieldNames[r.selfIndex];
var context = this.localNames[r.contextIndex];
return structuralCheckTemplate(r.selfIndex - 1, field, context, this.genNotify(r));
}
genReferenceCheck(r:ProtoRecord):string {
var newValue = this.localNames[r.selfIndex];
var oldValue = this.fieldNames[r.selfIndex];
var assignment = this.genUpdateCurrentValue(r);
var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
var notify = this.genNotify(r);
return referenceCheckTemplate(assignment, newValue, oldValue, r.lastInBinding ? addRecord : '', notify);
}
genUpdateCurrentValue(r:ProtoRecord):string {
var context = this.localNames[r.contextIndex];
var newValue = this.localNames[r.selfIndex];
var args = this.genArgs(r);
switch (r.mode) {
case RECORD_TYPE_SELF:
throw new BaseException("Cannot evaluate self");
case RECORD_TYPE_CONST:
return `${newValue} = ${this.genLiteral(r.funcOrValue)}`;
case RECORD_TYPE_PROPERTY:
if (r.contextIndex == 0) { // only the first property read can be a local
return propertyReadTemplate(r.name, context, newValue);
} else {
return assignmentTemplate(newValue, `${context}.${r.name}`);
}
case RECORD_TYPE_INVOKE_METHOD:
return assignmentTemplate(newValue, `${context}.${r.name}(${args})`);
case RECORD_TYPE_INVOKE_CLOSURE:
return assignmentTemplate(newValue, `${context}(${args})`);
case RECORD_TYPE_PRIMITIVE_OP:
return assignmentTemplate(newValue, `${UTIL}.${r.name}(${args})`);
case RECORD_TYPE_INTERPOLATE:
return assignmentTemplate(newValue, this.genInterpolation(r));
case RECORD_TYPE_KEYED_ACCESS:
var key = this.localNames[r.args[0]];
return assignmentTemplate(newValue, `${context}[${key}]`);
case RECORD_TYPE_INVOKE_FORMATTER:
return assignmentTemplate(newValue, `${FORMATTERS_ACCESSOR}.get("${r.name}")(${args})`);
default:
throw new BaseException(`Unknown operation ${r.mode}`);
}
}
genInterpolation(r:ProtoRecord):string{
var res = "";
for (var i = 0; i < r.args.length; ++i) {
res += this.genLiteral(r.fixedArgs[i]);
res += " + ";
res += this.localNames[r.args[i]];
res += " + ";
}
res += this.genLiteral(r.fixedArgs[r.args.length]);
return res;
}
genLiteral(value):string {
return JSON.stringify(value);
}
genNotify(r):string{
return r.lastInGroup ? notifyTemplate(r.selfIndex - 1) : '';
}
genArgs(r:ProtoRecord):string {
return r.args.map((arg) => this.localNames[arg]).join(", ");
}
}

View File

@ -0,0 +1,138 @@
import {isPresent, isBlank, BaseException, Type} from 'facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/collection';
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
import {ArrayChanges} from './array_changes';
import {KeyValueChanges} from './keyvalue_changes';
import {ProtoRecord} from './proto_change_detector';
import {ExpressionChangedAfterItHasBeenChecked} from './exceptions';
import {ChangeRecord} from './interfaces';
export var uninitialized = new Object();
export class SimpleChange {
previousValue:any;
currentValue:any;
constructor(previousValue:any, currentValue:any) {
this.previousValue = previousValue;
this.currentValue = currentValue;
}
}
export class ChangeDetectionUtil {
static unitialized() {
return uninitialized;
}
static arrayFn0() { return []; }
static arrayFn1(a1) { return [a1]; }
static arrayFn2(a1, a2) { return [a1, a2]; }
static arrayFn3(a1, a2, a3) { return [a1, a2, a3]; }
static arrayFn4(a1, a2, a3, a4) { return [a1, a2, a3, a4]; }
static arrayFn5(a1, a2, a3, a4, a5) { return [a1, a2, a3, a4, a5]; }
static arrayFn6(a1, a2, a3, a4, a5, a6) { return [a1, a2, a3, a4, a5, a6]; }
static arrayFn7(a1, a2, a3, a4, a5, a6, a7) { return [a1, a2, a3, a4, a5, a6, a7]; }
static arrayFn8(a1, a2, a3, a4, a5, a6, a7, a8) { return [a1, a2, a3, a4, a5, a6, a7, a8]; }
static arrayFn9(a1, a2, a3, a4, a5, a6, a7, a8, a9) { return [a1, a2, a3, a4, a5, a6, a7, a8, a9]; }
static operation_negate(value) {return !value;}
static operation_add(left, right) {return left + right;}
static operation_subtract(left, right) {return left - right;}
static operation_multiply(left, right) {return left * right;}
static operation_divide(left, right) {return left / right;}
static operation_remainder(left, right) {return left % right;}
static operation_equals(left, right) {return left == right;}
static operation_not_equals(left, right) {return left != right;}
static operation_less_then(left, right) {return left < right;}
static operation_greater_then(left, right) {return left > right;}
static operation_less_or_equals_then(left, right) {return left <= right;}
static operation_greater_or_equals_then(left, right) {return left >= right;}
static operation_logical_and(left, right) {return left && right;}
static operation_logical_or(left, right) {return left || right;}
static cond(cond, trueVal, falseVal) {return cond ? trueVal : falseVal;}
static mapFn(keys:List) {
function buildMap(values) {
var res = StringMapWrapper.create();
for(var i = 0; i < keys.length; ++i) {
StringMapWrapper.set(res, keys[i], values[i]);
}
return res;
}
switch (keys.length) {
case 0: return () => [];
case 1: return (a1) => buildMap([a1]);
case 2: return (a1, a2) => buildMap([a1, a2]);
case 3: return (a1, a2, a3) => buildMap([a1, a2, a3]);
case 4: return (a1, a2, a3, a4) => buildMap([a1, a2, a3, a4]);
case 5: return (a1, a2, a3, a4, a5) => buildMap([a1, a2, a3, a4, a5]);
case 6: return (a1, a2, a3, a4, a5, a6) => buildMap([a1, a2, a3, a4, a5, a6]);
case 7: return (a1, a2, a3, a4, a5, a6, a7) => buildMap([a1, a2, a3, a4, a5, a6, a7]);
case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8]);
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8, a9]);
default: throw new BaseException(`Does not support literal maps with more than 9 elements`);
}
}
static keyedAccess(obj, args) {
return obj[args[0]];
}
static structuralCheck(self, context) {
if (isBlank(self) || self === uninitialized) {
if (ArrayChanges.supports(context)) {
self = new ArrayChanges();
} else if (KeyValueChanges.supports(context)) {
self = new KeyValueChanges();
}
}
if (isBlank(context) || context === uninitialized) {
return new SimpleChange(null, null);
} else {
if (ArrayChanges.supports(context)) {
if (self.check(context)) {
return new SimpleChange(null, self); // TODO: don't wrap and return self instead
} else {
return null;
}
} else if (KeyValueChanges.supports(context)) {
if (self.check(context)) {
return new SimpleChange(null, self); // TODO: don't wrap and return self instead
} else {
return null;
}
} else {
throw new BaseException(`Unsupported type (${context})`);
}
}
}
static findContext(name:string, c){
while (c instanceof ContextWithVariableBindings) {
if (c.hasBinding(name)) {
return c;
}
c = c.parent;
}
return c;
}
static throwOnChange(proto:ProtoRecord, change) {
throw new ExpressionChangedAfterItHasBeenChecked(proto, change);
}
static changeRecord(memento:any, change:any):ChangeRecord {
return new ChangeRecord(memento, change);
}
static simpleChangeRecord(memento:any, previousValue:any, currentValue:any):ChangeRecord {
return new ChangeRecord(memento, new SimpleChange(previousValue, currentValue));
}
}

View File

@ -2,6 +2,9 @@ import {isPresent, isBlank, BaseException, FunctionWrapper} from 'facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/collection'; import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/collection';
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings'; import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
import {AbstractChangeDetector} from './abstract_change_detector';
import {ChangeDetectionUtil, SimpleChange, uninitialized} from './change_detection_util';
import {ArrayChanges} from './array_changes'; import {ArrayChanges} from './array_changes';
import {KeyValueChanges} from './keyvalue_changes'; import {KeyValueChanges} from './keyvalue_changes';
@ -12,76 +15,37 @@ import {
RECORD_TYPE_INVOKE_METHOD, RECORD_TYPE_INVOKE_METHOD,
RECORD_TYPE_CONST, RECORD_TYPE_CONST,
RECORD_TYPE_INVOKE_CLOSURE, RECORD_TYPE_INVOKE_CLOSURE,
RECORD_TYPE_INVOKE_PURE_FUNCTION, RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_INVOKE_FORMATTER, RECORD_TYPE_INVOKE_FORMATTER,
RECORD_TYPE_STRUCTURAL_CHECK, RECORD_TYPE_STRUCTURAL_CHECK,
RECORD_TYPE_INTERPOLATE,
ProtoChangeDetector ProtoChangeDetector
} from './proto_change_detector'; } from './proto_change_detector';
import {ChangeDetector, ChangeRecord, ChangeDispatcher} from './interfaces'; import {ChangeDetector, ChangeDispatcher} from './interfaces';
import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions'; import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions';
var _uninitialized = new Object(); export class DynamicChangeDetector extends AbstractChangeDetector {
class SimpleChange {
previousValue:any;
currentValue:any;
constructor(previousValue:any, currentValue:any) {
this.previousValue = previousValue;
this.currentValue = currentValue;
}
}
export class DynamicChangeDetector extends ChangeDetector {
dispatcher:any; dispatcher:any;
formatters:Map; formatters:Map;
children:List;
values:List; values:List;
protos:List<ProtoRecord>; protos:List<ProtoRecord>;
parent:ChangeDetector;
constructor(dispatcher:any, formatters:Map, protoRecords:List<ProtoRecord>) { constructor(dispatcher:any, formatters:Map, protoRecords:List<ProtoRecord>) {
super();
this.dispatcher = dispatcher; this.dispatcher = dispatcher;
this.formatters = formatters; this.formatters = formatters;
this.values = ListWrapper.createFixedSize(protoRecords.length + 1); this.values = ListWrapper.createFixedSize(protoRecords.length + 1);
ListWrapper.fill(this.values, _uninitialized); ListWrapper.fill(this.values, uninitialized);
this.protos = protoRecords; this.protos = protoRecords;
this.children = [];
}
addChild(cd:ChangeDetector) {
ListWrapper.push(this.children, cd);
cd.parent = this;
}
removeChild(cd:ChangeDetector) {
ListWrapper.remove(this.children, cd);
}
remove() {
this.parent.removeChild(this);
} }
setContext(context:any) { setContext(context:any) {
this.values[0] = context; this.values[0] = context;
} }
detectChanges() { detectChangesInRecords(throwOnChange:boolean) {
this._detectChanges(false);
}
checkNoChanges() {
this._detectChanges(true);
}
_detectChanges(throwOnChange:boolean) {
this._detectChangesInRecords(throwOnChange);
this._detectChangesInChildren(throwOnChange);
}
_detectChangesInRecords(throwOnChange:boolean) {
var protos:List<ProtoRecord> = this.protos; var protos:List<ProtoRecord> = this.protos;
var updatedRecords = null; var updatedRecords = null;
@ -91,21 +55,16 @@ export class DynamicChangeDetector extends ChangeDetector {
var proto:ProtoRecord = protos[i]; var proto:ProtoRecord = protos[i];
var change = this._check(proto); var change = this._check(proto);
// only when the terminal record, which ends a binding, changes if (isPresent(change)) {
// we need to add it to a list of changed records
if (isPresent(change) && proto.terminal) {
if (throwOnChange) throw new ExpressionChangedAfterItHasBeenChecked(proto, change);
currentGroup = proto.groupMemento; currentGroup = proto.groupMemento;
updatedRecords = this._addRecord(updatedRecords, proto, change); updatedRecords = this._addRecord(updatedRecords, proto, change);
} }
if (isPresent(updatedRecords)) { if (proto.lastInGroup && isPresent(updatedRecords)) {
var lastRecordOfCurrentGroup = protos.length == i + 1 || if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, updatedRecords[0]);
currentGroup !== protos[i + 1].groupMemento;
if (lastRecordOfCurrentGroup) { this.dispatcher.onRecordChange(currentGroup, updatedRecords);
this.dispatcher.onRecordChange(currentGroup, updatedRecords); updatedRecords = null;
updatedRecords = null;
}
} }
} }
} }
@ -128,7 +87,11 @@ export class DynamicChangeDetector extends ChangeDetector {
if (!isSame(prevValue, currValue)) { if (!isSame(prevValue, currValue)) {
this._writeSelf(proto, currValue); this._writeSelf(proto, currValue);
return new SimpleChange(prevValue === _uninitialized ? null : prevValue, currValue); if (proto.lastInBinding) {
return new SimpleChange(prevValue, currValue);
} else {
return null;
}
} else { } else {
return null; return null;
} }
@ -144,23 +107,28 @@ export class DynamicChangeDetector extends ChangeDetector {
case RECORD_TYPE_PROPERTY: case RECORD_TYPE_PROPERTY:
var context = this._readContext(proto); var context = this._readContext(proto);
while (context instanceof ContextWithVariableBindings) { var c = ChangeDetectionUtil.findContext(proto.name, context);
if (context.hasBinding(proto.name)) { if (c instanceof ContextWithVariableBindings) {
return context.get(proto.name); return c.get(proto.name);
} } else {
context = context.parent; var propertyGetter:Function = proto.funcOrValue;
return propertyGetter(c);
} }
var propertyGetter:Function = proto.funcOrValue; break;
return propertyGetter(context);
case RECORD_TYPE_INVOKE_METHOD: case RECORD_TYPE_INVOKE_METHOD:
var methodInvoker:Function = proto.funcOrValue; var methodInvoker:Function = proto.funcOrValue;
return methodInvoker(this._readContext(proto), this._readArgs(proto)); return methodInvoker(this._readContext(proto), this._readArgs(proto));
case RECORD_TYPE_KEYED_ACCESS:
var arg = this._readArgs(proto)[0];
return this._readContext(proto)[arg];
case RECORD_TYPE_INVOKE_CLOSURE: case RECORD_TYPE_INVOKE_CLOSURE:
return FunctionWrapper.apply(this._readContext(proto), this._readArgs(proto)); return FunctionWrapper.apply(this._readContext(proto), this._readArgs(proto));
case RECORD_TYPE_INVOKE_PURE_FUNCTION: case RECORD_TYPE_INTERPOLATE:
case RECORD_TYPE_PRIMITIVE_OP:
return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto)); return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto));
case RECORD_TYPE_INVOKE_FORMATTER: case RECORD_TYPE_INVOKE_FORMATTER:
@ -176,39 +144,16 @@ export class DynamicChangeDetector extends ChangeDetector {
var self = this._readSelf(proto); var self = this._readSelf(proto);
var context = this._readContext(proto); var context = this._readContext(proto);
if (isBlank(self) || self === _uninitialized) { var change = ChangeDetectionUtil.structuralCheck(self, context);
if (ArrayChanges.supports(context)) { if (isPresent(change)) {
self = new ArrayChanges(); this._writeSelf(proto, change.currentValue);
} else if (KeyValueChanges.supports(context)) {
self = new KeyValueChanges();
}
} }
return change;
if (ArrayChanges.supports(context)) {
if (self.check(context)) {
this._writeSelf(proto, self);
return new SimpleChange(null, self); // TODO: don't wrap and return self instead
}
} else if (KeyValueChanges.supports(context)) {
if (self.check(context)) {
this._writeSelf(proto, self);
return new SimpleChange(null, self); // TODO: don't wrap and return self instead
}
} else if (context == null) {
this._writeSelf(proto, null);
return new SimpleChange(null, null);
} else {
throw new BaseException(`Unsupported type (${context})`);
}
} }
_addRecord(updatedRecords:List, proto:ProtoRecord, change):List { _addRecord(updatedRecords:List, proto:ProtoRecord, change):List {
// we can use a pool of change records not to create extra garbage // we can use a pool of change records not to create extra garbage
var record = new ChangeRecord(proto.bindingMemento, change); var record = ChangeDetectionUtil.changeRecord(proto.bindingMemento, change);
if (isBlank(updatedRecords)) { if (isBlank(updatedRecords)) {
updatedRecords = _singleElementList; updatedRecords = _singleElementList;
updatedRecords[0] = record; updatedRecords[0] = record;
@ -222,23 +167,16 @@ export class DynamicChangeDetector extends ChangeDetector {
return updatedRecords; return updatedRecords;
} }
_detectChangesInChildren(throwOnChange:boolean) {
var children = this.children;
for(var i = 0; i < children.length; ++i) {
children[i]._detectChanges(throwOnChange);
}
}
_readContext(proto:ProtoRecord) { _readContext(proto:ProtoRecord) {
return this.values[proto.contextIndex]; return this.values[proto.contextIndex];
} }
_readSelf(proto:ProtoRecord) { _readSelf(proto:ProtoRecord) {
return this.values[proto.record_type_selfIndex]; return this.values[proto.selfIndex];
} }
_writeSelf(proto:ProtoRecord, value) { _writeSelf(proto:ProtoRecord, value) {
this.values[proto.record_type_selfIndex] = value; this.values[proto.selfIndex] = value;
} }
_readArgs(proto:ProtoRecord) { _readArgs(proto:ProtoRecord) {

View File

@ -1,4 +1,4 @@
import {isPresent, isBlank, BaseException} from 'facade/lang'; import {isPresent, isBlank, BaseException, Type, isString} from 'facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/collection'; import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/collection';
import { import {
@ -24,55 +24,118 @@ import {
} from './parser/ast'; } from './parser/ast';
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings'; import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
import {ChangeDispatcher, ChangeDetector} from './interfaces'; import {ChangeRecord, ChangeDispatcher, ChangeDetector} from './interfaces';
import {ChangeDetectionUtil} from './change_detection_util';
import {DynamicChangeDetector} from './dynamic_change_detector'; import {DynamicChangeDetector} from './dynamic_change_detector';
import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
import {ArrayChanges} from './array_changes';
import {KeyValueChanges} from './keyvalue_changes';
export const RECORD_TYPE_SELF = 0; export const RECORD_TYPE_SELF = 0;
export const RECORD_TYPE_PROPERTY = 1; export const RECORD_TYPE_CONST = 1;
export const RECORD_TYPE_INVOKE_METHOD = 2; export const RECORD_TYPE_PRIMITIVE_OP = 2;
export const RECORD_TYPE_CONST = 3; export const RECORD_TYPE_PROPERTY = 3;
export const RECORD_TYPE_INVOKE_CLOSURE = 4; export const RECORD_TYPE_INVOKE_METHOD = 4;
export const RECORD_TYPE_INVOKE_PURE_FUNCTION = 5; export const RECORD_TYPE_INVOKE_CLOSURE = 5;
export const RECORD_TYPE_INVOKE_FORMATTER = 6; export const RECORD_TYPE_KEYED_ACCESS = 6;
export const RECORD_TYPE_STRUCTURAL_CHECK = 10; export const RECORD_TYPE_INVOKE_FORMATTER = 7;
export const RECORD_TYPE_STRUCTURAL_CHECK = 8;
export const RECORD_TYPE_INTERPOLATE = 9;
export class ProtoRecord { export class ProtoRecord {
mode:number; mode:number;
name:string; name:string;
funcOrValue:any; funcOrValue:any;
args:List; args:List;
fixedArgs:List;
contextIndex:number; contextIndex:number;
record_type_selfIndex:number; selfIndex:number;
bindingMemento:any; bindingMemento:any;
groupMemento:any; groupMemento:any;
terminal:boolean; lastInBinding:boolean;
lastInGroup:boolean;
expressionAsString:string; expressionAsString:string;
constructor(mode:number, constructor(mode:number,
name:string, name:string,
funcOrValue, funcOrValue,
args:List, args:List,
fixedArgs:List,
contextIndex:number, contextIndex:number,
record_type_selfIndex:number, selfIndex:number,
bindingMemento:any, bindingMemento:any,
groupMemento:any, groupMemento:any,
terminal:boolean,
expressionAsString:string) { expressionAsString:string) {
this.mode = mode; this.mode = mode;
this.name = name; this.name = name;
this.funcOrValue = funcOrValue; this.funcOrValue = funcOrValue;
this.args = args; this.args = args;
this.fixedArgs = fixedArgs;
this.contextIndex = contextIndex; this.contextIndex = contextIndex;
this.record_type_selfIndex = record_type_selfIndex; this.selfIndex = selfIndex;
this.bindingMemento = bindingMemento; this.bindingMemento = bindingMemento;
this.groupMemento = groupMemento; this.groupMemento = groupMemento;
this.terminal = terminal; this.lastInBinding = false;
this.lastInGroup = false;
this.expressionAsString = expressionAsString; this.expressionAsString = expressionAsString;
} }
} }
export class ProtoChangeDetector { export class ProtoChangeDetector {
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false){}
instantiate(dispatcher:any, formatters:Map):ChangeDetector{
return null;
}
}
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
_recordBuilder:ProtoRecordBuilder;
constructor() {
this._recordBuilder = new ProtoRecordBuilder();
}
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
this._recordBuilder.addAst(ast, bindingMemento, groupMemento, structural);
}
instantiate(dispatcher:any, formatters:Map) {
var records = this._recordBuilder.records;
return new DynamicChangeDetector(dispatcher, formatters, records);
}
}
var _jitProtoChangeDetectorClassCounter:number = 0;
export class JitProtoChangeDetector extends ProtoChangeDetector {
_factory:Function;
_recordBuilder:ProtoRecordBuilder;
constructor() {
this._recordBuilder = new ProtoRecordBuilder();
}
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
this._recordBuilder.addAst(ast, bindingMemento, groupMemento, structural);
}
instantiate(dispatcher:any, formatters:Map) {
this._createFactoryIfNecessary();
return this._factory(dispatcher, formatters);
}
_createFactoryIfNecessary() {
if (isBlank(this._factory)) {
var c = _jitProtoChangeDetectorClassCounter++;
var records = this._recordBuilder.records;
var typeName = `ChangeDetector${c}`;
this._factory = new ChangeDetectorJITGenerator(typeName, records).generate();
}
}
}
class ProtoRecordBuilder {
records:List<ProtoRecord>; records:List<ProtoRecord>;
constructor() { constructor() {
@ -82,23 +145,23 @@ export class ProtoChangeDetector {
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) { addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
if (structural) ast = new Structural(ast); if (structural) ast = new Structural(ast);
var c = new ProtoOperationsCreator(bindingMemento, groupMemento, var last = ListWrapper.last(this.records);
this.records.length, ast.toString()); if (isPresent(last) && last.groupMemento == groupMemento) {
ast.visit(c); last.lastInGroup = false;
if (! ListWrapper.isEmpty(c.protoRecords)) {
var last = ListWrapper.last(c.protoRecords);
last.terminal = true;
this.records = ListWrapper.concat(this.records, c.protoRecords);
} }
}
instantiate(dispatcher:any, formatters:Map) { var pr = _ConvertAstIntoProtoRecords.convert(ast, bindingMemento, groupMemento, this.records.length);
return new DynamicChangeDetector(dispatcher, formatters, this.records); if (! ListWrapper.isEmpty(pr)) {
var last = ListWrapper.last(pr);
last.lastInBinding = true;
last.lastInGroup = true;
this.records = ListWrapper.concat(this.records, pr);
}
} }
} }
class ProtoOperationsCreator { class _ConvertAstIntoProtoRecords {
protoRecords:List; protoRecords:List;
bindingMemento:any; bindingMemento:any;
groupMemento:any; groupMemento:any;
@ -113,77 +176,89 @@ class ProtoOperationsCreator {
this.expressionAsString = expressionAsString; this.expressionAsString = expressionAsString;
} }
static convert(ast:AST, bindingMemento:any, groupMemento:any, contextIndex:number) {
var c = new _ConvertAstIntoProtoRecords(bindingMemento, groupMemento, contextIndex, ast.toString());
ast.visit(c);
return c.protoRecords;
}
visitImplicitReceiver(ast:ImplicitReceiver) { visitImplicitReceiver(ast:ImplicitReceiver) {
return 0; return 0;
} }
visitInterpolation(ast:Interpolation) { visitInterpolation(ast:Interpolation) {
var args = this._visitAll(ast.expressions); var args = this._visitAll(ast.expressions);
return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, "Interpolate()", _interpolationFn(ast.strings), args, 0); return this._addRecord(RECORD_TYPE_INTERPOLATE, "interpolate", _interpolationFn(ast.strings),
args, ast.strings, 0);
} }
visitLiteralPrimitive(ast:LiteralPrimitive) { visitLiteralPrimitive(ast:LiteralPrimitive) {
return this._addRecord(RECORD_TYPE_CONST, null, ast.value, [], 0); return this._addRecord(RECORD_TYPE_CONST, "literal", ast.value, [], null, 0);
} }
visitAccessMember(ast:AccessMember) { visitAccessMember(ast:AccessMember) {
var receiver = ast.receiver.visit(this); var receiver = ast.receiver.visit(this);
return this._addRecord(RECORD_TYPE_PROPERTY, ast.name, ast.getter, [], receiver); return this._addRecord(RECORD_TYPE_PROPERTY, ast.name, ast.getter, [], null, receiver);
} }
visitFormatter(ast:Formatter) { visitFormatter(ast:Formatter) {
return this._addRecord(RECORD_TYPE_INVOKE_FORMATTER, ast.name, ast.name, this._visitAll(ast.allArgs), 0); return this._addRecord(RECORD_TYPE_INVOKE_FORMATTER, ast.name, ast.name, this._visitAll(ast.allArgs), null, 0);
} }
visitMethodCall(ast:MethodCall) { visitMethodCall(ast:MethodCall) {
var receiver = ast.receiver.visit(this); var receiver = ast.receiver.visit(this);
var args = this._visitAll(ast.args); var args = this._visitAll(ast.args);
return this._addRecord(RECORD_TYPE_INVOKE_METHOD, ast.name, ast.fn, args, receiver); return this._addRecord(RECORD_TYPE_INVOKE_METHOD, ast.name, ast.fn, args, null, receiver);
} }
visitFunctionCall(ast:FunctionCall) { visitFunctionCall(ast:FunctionCall) {
var target = ast.target.visit(this); var target = ast.target.visit(this);
var args = this._visitAll(ast.args); var args = this._visitAll(ast.args);
return this._addRecord(RECORD_TYPE_INVOKE_CLOSURE, null, null, args, target); return this._addRecord(RECORD_TYPE_INVOKE_CLOSURE, "closure", null, args, null, target);
} }
visitLiteralArray(ast:LiteralArray) { visitLiteralArray(ast:LiteralArray) {
return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, "Array()", _arrayFn(ast.expressions.length), var primitiveName = `arrayFn${ast.expressions.length}`;
this._visitAll(ast.expressions), 0); return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, primitiveName, _arrayFn(ast.expressions.length),
this._visitAll(ast.expressions), null, 0);
} }
visitLiteralMap(ast:LiteralMap) { visitLiteralMap(ast:LiteralMap) {
return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, "Map()", _mapFn(ast.keys, ast.values.length), return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, _mapPrimitiveName(ast.keys),
this._visitAll(ast.values), 0); ChangeDetectionUtil.mapFn(ast.keys), this._visitAll(ast.values), null, 0);
} }
visitBinary(ast:Binary) { visitBinary(ast:Binary) {
var left = ast.left.visit(this); var left = ast.left.visit(this);
var right = ast.right.visit(this); var right = ast.right.visit(this);
return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, ast.operation, _operationToFunction(ast.operation), [left, right], 0); return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, _operationToPrimitiveName(ast.operation),
_operationToFunction(ast.operation), [left, right], null, 0);
} }
visitPrefixNot(ast:PrefixNot) { visitPrefixNot(ast:PrefixNot) {
var exp = ast.expression.visit(this) var exp = ast.expression.visit(this)
return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, "-", _operation_negate, [exp], 0); return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, "operation_negate",
ChangeDetectionUtil.operation_negate, [exp], null, 0);
} }
visitConditional(ast:Conditional) { visitConditional(ast:Conditional) {
var c = ast.condition.visit(this); var c = ast.condition.visit(this);
var t = ast.trueExp.visit(this); var t = ast.trueExp.visit(this);
var f = ast.falseExp.visit(this); var f = ast.falseExp.visit(this);
return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, "?:", _cond, [c,t,f], 0); return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, "cond",
ChangeDetectionUtil.cond, [c,t,f], null, 0);
} }
visitStructural(ast:Structural) { visitStructural(ast:Structural) {
var value = ast.value.visit(this); var value = ast.value.visit(this);
return this._addRecord(RECORD_TYPE_STRUCTURAL_CHECK, "record_type_structural_check", null, [], value); return this._addRecord(RECORD_TYPE_STRUCTURAL_CHECK, "structural", null, [], null, value);
} }
visitKeyedAccess(ast:KeyedAccess) { visitKeyedAccess(ast:KeyedAccess) {
var obj = ast.obj.visit(this); var obj = ast.obj.visit(this);
var key = ast.key.visit(this); var key = ast.key.visit(this);
return this._addRecord(RECORD_TYPE_INVOKE_METHOD, "[]", _keyedAccess, [key], obj); return this._addRecord(RECORD_TYPE_KEYED_ACCESS, "keyedAccess",
ChangeDetectionUtil.keyedAccess, [key], null, obj);
} }
_visitAll(asts:List) { _visitAll(asts:List) {
@ -194,111 +269,93 @@ class ProtoOperationsCreator {
return res; return res;
} }
_addRecord(type, name, funcOrValue, args, context) { _addRecord(type, name, funcOrValue, args, fixedArgs, context) {
var record_type_selfIndex = ++ this.contextIndex; var selfIndex = ++ this.contextIndex;
ListWrapper.push(this.protoRecords, ListWrapper.push(this.protoRecords,
new ProtoRecord(type, name, funcOrValue, args, context, record_type_selfIndex, new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, selfIndex,
this.bindingMemento, this.groupMemento, false, this.expressionAsString)); this.bindingMemento, this.groupMemento, this.expressionAsString));
return record_type_selfIndex; return selfIndex;
} }
} }
function _arrayFn(length:int) {
switch (length) {
case 0: return () => [];
case 1: return (a1) => [a1];
case 2: return (a1, a2) => [a1, a2];
case 3: return (a1, a2, a3) => [a1, a2, a3];
case 4: return (a1, a2, a3, a4) => [a1, a2, a3, a4];
case 5: return (a1, a2, a3, a4, a5) => [a1, a2, a3, a4, a5];
case 6: return (a1, a2, a3, a4, a5, a6) => [a1, a2, a3, a4, a5, a6];
case 7: return (a1, a2, a3, a4, a5, a6, a7) => [a1, a2, a3, a4, a5, a6, a7];
case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => [a1, a2, a3, a4, a5, a6, a7, a8];
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => [a1, a2, a3, a4, a5, a6, a7, a8, a9];
default: throw new BaseException(`Does not support literal arrays with more than 9 elements`);
}
}
function _mapFn(keys:List, length:int) {
function buildMap(values) {
var res = StringMapWrapper.create();
for(var i = 0; i < keys.length; ++i) {
StringMapWrapper.set(res, keys[i], values[i]);
}
return res;
}
function _arrayFn(length:number):Function {
switch (length) { switch (length) {
case 0: return () => []; case 0: return ChangeDetectionUtil.arrayFn0;
case 1: return (a1) => buildMap([a1]); case 1: return ChangeDetectionUtil.arrayFn1;
case 2: return (a1, a2) => buildMap([a1, a2]); case 2: return ChangeDetectionUtil.arrayFn2;
case 3: return (a1, a2, a3) => buildMap([a1, a2, a3]); case 3: return ChangeDetectionUtil.arrayFn3;
case 4: return (a1, a2, a3, a4) => buildMap([a1, a2, a3, a4]); case 4: return ChangeDetectionUtil.arrayFn4;
case 5: return (a1, a2, a3, a4, a5) => buildMap([a1, a2, a3, a4, a5]); case 5: return ChangeDetectionUtil.arrayFn5;
case 6: return (a1, a2, a3, a4, a5, a6) => buildMap([a1, a2, a3, a4, a5, a6]); case 6: return ChangeDetectionUtil.arrayFn6;
case 7: return (a1, a2, a3, a4, a5, a6, a7) => buildMap([a1, a2, a3, a4, a5, a6, a7]); case 7: return ChangeDetectionUtil.arrayFn7;
case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8]); case 8: return ChangeDetectionUtil.arrayFn8;
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8, a9]); case 9: return ChangeDetectionUtil.arrayFn9;
default: throw new BaseException(`Does not support literal maps with more than 9 elements`); default: throw new BaseException(`Does not support literal maps with more than 9 elements`);
} }
} }
function _mapPrimitiveName(keys:List) {
var stringifiedKeys = ListWrapper.join(
ListWrapper.map(keys, (k) => isString(k) ? `"${k}"` : `${k}`),
", ");
return `mapFn([${stringifiedKeys}])`;
}
function _operationToPrimitiveName(operation:string):string {
switch(operation) {
case '+' : return "operation_add";
case '-' : return "operation_subtract";
case '*' : return "operation_multiply";
case '/' : return "operation_divide";
case '%' : return "operation_remainder";
case '==' : return "operation_equals";
case '!=' : return "operation_not_equals";
case '<' : return "operation_less_then";
case '>' : return "operation_greater_then";
case '<=' : return "operation_less_or_equals_then";
case '>=' : return "operation_greater_or_equals_then";
case '&&' : return "operation_logical_and";
case '||' : return "operation_logical_or";
default: throw new BaseException(`Unsupported operation ${operation}`);
}
}
function _operationToFunction(operation:string):Function { function _operationToFunction(operation:string):Function {
switch(operation) { switch(operation) {
case '+' : return _operation_add; case '+' : return ChangeDetectionUtil.operation_add;
case '-' : return _operation_subtract; case '-' : return ChangeDetectionUtil.operation_subtract;
case '*' : return _operation_multiply; case '*' : return ChangeDetectionUtil.operation_multiply;
case '/' : return _operation_divide; case '/' : return ChangeDetectionUtil.operation_divide;
case '%' : return _operation_remainder; case '%' : return ChangeDetectionUtil.operation_remainder;
case '==' : return _operation_equals; case '==' : return ChangeDetectionUtil.operation_equals;
case '!=' : return _operation_not_equals; case '!=' : return ChangeDetectionUtil.operation_not_equals;
case '<' : return _operation_less_then; case '<' : return ChangeDetectionUtil.operation_less_then;
case '>' : return _operation_greater_then; case '>' : return ChangeDetectionUtil.operation_greater_then;
case '<=' : return _operation_less_or_equals_then; case '<=' : return ChangeDetectionUtil.operation_less_or_equals_then;
case '>=' : return _operation_greater_or_equals_then; case '>=' : return ChangeDetectionUtil.operation_greater_or_equals_then;
case '&&' : return _operation_logical_and; case '&&' : return ChangeDetectionUtil.operation_logical_and;
case '||' : return _operation_logical_or; case '||' : return ChangeDetectionUtil.operation_logical_or;
default: throw new BaseException(`Unsupported operation ${operation}`); default: throw new BaseException(`Unsupported operation ${operation}`);
} }
} }
function _operation_negate(value) {return !value;}
function _operation_add(left, right) {return left + right;}
function _operation_subtract(left, right) {return left - right;}
function _operation_multiply(left, right) {return left * right;}
function _operation_divide(left, right) {return left / right;}
function _operation_remainder(left, right) {return left % right;}
function _operation_equals(left, right) {return left == right;}
function _operation_not_equals(left, right) {return left != right;}
function _operation_less_then(left, right) {return left < right;}
function _operation_greater_then(left, right) {return left > right;}
function _operation_less_or_equals_then(left, right) {return left <= right;}
function _operation_greater_or_equals_then(left, right) {return left >= right;}
function _operation_logical_and(left, right) {return left && right;}
function _operation_logical_or(left, right) {return left || right;}
function _cond(cond, trueVal, falseVal) {return cond ? trueVal : falseVal;}
function _keyedAccess(obj, args) {
return obj[args[0]];
}
function s(v) { function s(v) {
return isPresent(v) ? '' + v : ''; return isPresent(v) ? '' + v : '';
} }
function _interpolationFn(strings:List) { function _interpolationFn(strings:List) {
var length = strings.length; var length = strings.length;
var i = -1; var c0 = length > 0 ? strings[0] : null;
var c0 = length > ++i ? strings[i] : null; var c1 = length > 1 ? strings[1] : null;
var c1 = length > ++i ? strings[i] : null; var c2 = length > 2 ? strings[2] : null;
var c2 = length > ++i ? strings[i] : null; var c3 = length > 3 ? strings[3] : null;
var c3 = length > ++i ? strings[i] : null; var c4 = length > 4 ? strings[4] : null;
var c4 = length > ++i ? strings[i] : null; var c5 = length > 5 ? strings[5] : null;
var c5 = length > ++i ? strings[i] : null; var c6 = length > 6 ? strings[6] : null;
var c6 = length > ++i ? strings[i] : null; var c7 = length > 7 ? strings[7] : null;
var c7 = length > ++i ? strings[i] : null; var c8 = length > 8 ? strings[8] : null;
var c8 = length > ++i ? strings[i] : null; var c9 = length > 9 ? strings[9] : null;
var c9 = length > ++i ? strings[i] : null;
switch (length - 1) { switch (length - 1) {
case 1: return (a1) => c0 + s(a1) + c1; case 1: return (a1) => c0 + s(a1) + c1;
case 2: return (a1, a2) => c0 + s(a1) + c1 + s(a2) + c2; case 2: return (a1, a2) => c0 + s(a1) + c1 + s(a2) + c2;
@ -311,4 +368,4 @@ function _interpolationFn(strings:List) {
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7 + s(a8) + c8 + s(a9) + c9; case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7 + s(a8) + c8 + s(a9) + c9;
default: throw new BaseException(`Does not support more than 9 expressions`); default: throw new BaseException(`Does not support more than 9 expressions`);
} }
} }

View File

@ -1,4 +1,4 @@
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'test_lib/test_lib'; import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, IS_DARTIUM} from 'test_lib/test_lib';
import {isPresent, isBlank, isJsObject, BaseException, FunctionWrapper} from 'facade/lang'; import {isPresent, isBlank, isJsObject, BaseException, FunctionWrapper} from 'facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/collection'; import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/collection';
@ -8,398 +8,442 @@ import {Lexer} from 'change_detection/parser/lexer';
import {reflector} from 'reflection/reflection'; import {reflector} from 'reflection/reflection';
import {arrayChangesAsString, kvChangesAsString} from './util'; import {arrayChangesAsString, kvChangesAsString} from './util';
import {ProtoChangeDetector, ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, ContextWithVariableBindings}
ContextWithVariableBindings}
from 'change_detection/change_detection'; from 'change_detection/change_detection';
import {JitProtoChangeDetector, DynamicProtoChangeDetector} from 'change_detection/proto_change_detector';
export function main() { export function main() {
function ast(exp:string, location:string = 'location') { describe("change detection", () => {
var parser = new Parser(new Lexer()); StringMapWrapper.forEach(
return parser.parseBinding(exp, location); { "dynamic": () => new DynamicProtoChangeDetector(),
} "JIT": () => new JitProtoChangeDetector()
}, (createProtoChangeDetector, name) => {
function createChangeDetector(memo:string, exp:string, context = null, formatters = null, if (name == "JIT" && IS_DARTIUM) return;
structural = false) {
var pcd = new ProtoChangeDetector();
pcd.addAst(ast(exp), memo, memo, structural);
var dispatcher = new TestDispatcher(); function ast(exp:string, location:string = 'location') {
var cd = pcd.instantiate(dispatcher, formatters); var parser = new Parser(new Lexer());
cd.setContext(context); return parser.parseBinding(exp, location);
return {"changeDetector" : cd, "dispatcher" : dispatcher};
}
function executeWatch(memo:string, exp:string, context = null, formatters = null,
content = false) {
var res = createChangeDetector(memo, exp, context, formatters, content);
res["changeDetector"].detectChanges();
return res["dispatcher"].log;
}
describe('change_detection', () => {
it('should do simple watching', () => {
var person = new Person("misko");
var c = createChangeDetector('name', 'name', person);
var cd = c["changeDetector"];
var dispatcher = c["dispatcher"];
cd.detectChanges();
expect(dispatcher.log).toEqual(['name=misko']);
dispatcher.clear();
cd.detectChanges();
expect(dispatcher.log).toEqual([]);
person.name = "Misko";
cd.detectChanges();
expect(dispatcher.log).toEqual(['name=Misko']);
});
it('should report all changes on the first run including uninitialized values', () => {
var uninit = new Uninitialized();
var c = createChangeDetector('value', 'value', uninit);
var cd = c["changeDetector"];
var dispatcher = c["dispatcher"];
cd.detectChanges();
expect(dispatcher.log).toEqual(['value=null']);
});
it("should support literals", () => {
expect(executeWatch('const', '10')).toEqual(['const=10']);
expect(executeWatch('const', '"str"')).toEqual(['const=str']);
});
it('simple chained property access', () => {
var address = new Address('Grenoble');
var person = new Person('Victor', address);
expect(executeWatch('address.city', 'address.city', person))
.toEqual(['address.city=Grenoble']);
});
it("should support method calls", () => {
var person = new Person('Victor');
expect(executeWatch('m', 'sayHi("Jim")', person)).toEqual(['m=Hi, Jim']);
});
it("should support function calls", () => {
var td = new TestData(() => (a) => a);
expect(executeWatch('value', 'a()(99)', td)).toEqual(['value=99']);
});
it("should support chained method calls", () => {
var person = new Person('Victor');
var td = new TestData(person);
expect(executeWatch('m', 'a.sayHi("Jim")', td)).toEqual(['m=Hi, Jim']);
});
it("should support literal array", () => {
var c = createChangeDetector('array', '[1,2]');
c["changeDetector"].detectChanges();
expect(c["dispatcher"].loggedValues).toEqual([[[1,2]]]);
c = createChangeDetector('array', '[1,a]', new TestData(2));
c["changeDetector"].detectChanges();
expect(c["dispatcher"].loggedValues).toEqual([[[1,2]]]);
});
it("should support literal maps", () => {
var c = createChangeDetector('map', '{z:1}');
c["changeDetector"].detectChanges();
expect(c["dispatcher"].loggedValues[0][0]['z']).toEqual(1);
c = createChangeDetector('map', '{z:a}', new TestData(1));
c["changeDetector"].detectChanges();
expect(c["dispatcher"].loggedValues[0][0]['z']).toEqual(1);
});
it("should support binary operations", () => {
expect(executeWatch('exp', '10 + 2')).toEqual(['exp=12']);
expect(executeWatch('exp', '10 - 2')).toEqual(['exp=8']);
expect(executeWatch('exp', '10 * 2')).toEqual(['exp=20']);
expect(executeWatch('exp', '10 / 2')).toEqual([`exp=${5.0}`]); //dart exp=5.0, js exp=5
expect(executeWatch('exp', '11 % 2')).toEqual(['exp=1']);
expect(executeWatch('exp', '1 == 1')).toEqual(['exp=true']);
expect(executeWatch('exp', '1 != 1')).toEqual(['exp=false']);
expect(executeWatch('exp', '1 < 2')).toEqual(['exp=true']);
expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']);
expect(executeWatch('exp', '2 > 1')).toEqual(['exp=true']);
expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']);
expect(executeWatch('exp', '1 <= 2')).toEqual(['exp=true']);
expect(executeWatch('exp', '2 <= 2')).toEqual(['exp=true']);
expect(executeWatch('exp', '2 <= 1')).toEqual(['exp=false']);
expect(executeWatch('exp', '2 >= 1')).toEqual(['exp=true']);
expect(executeWatch('exp', '2 >= 2')).toEqual(['exp=true']);
expect(executeWatch('exp', '1 >= 2')).toEqual(['exp=false']);
expect(executeWatch('exp', 'true && true')).toEqual(['exp=true']);
expect(executeWatch('exp', 'true && false')).toEqual(['exp=false']);
expect(executeWatch('exp', 'true || false')).toEqual(['exp=true']);
expect(executeWatch('exp', 'false || false')).toEqual(['exp=false']);
});
it("should support negate", () => {
expect(executeWatch('exp', '!true')).toEqual(['exp=false']);
expect(executeWatch('exp', '!!true')).toEqual(['exp=true']);
});
it("should support conditionals", () => {
expect(executeWatch('m', '1 < 2 ? 1 : 2')).toEqual(['m=1']);
expect(executeWatch('m', '1 > 2 ? 1 : 2')).toEqual(['m=2']);
});
describe("keyed access", () => {
it("should support accessing a list item", () => {
expect(executeWatch('array[0]', '["foo", "bar"][0]')).toEqual(['array[0]=foo']);
});
it("should support accessing a map item", () => {
expect(executeWatch('map[foo]', '{"foo": "bar"}["foo"]')).toEqual(['map[foo]=bar']);
});
});
it("should support formatters", () => {
var formatters = MapWrapper.createFromPairs([
['uppercase', (v) => v.toUpperCase()],
['wrap', (v, before, after) => `${before}${v}${after}`]]);
expect(executeWatch('str', '"aBc" | uppercase', null, formatters)).toEqual(['str=ABC']);
expect(executeWatch('str', '"b" | wrap:"a":"c"', null, formatters)).toEqual(['str=abc']);
});
describe("group changes", () => {
it("should notify the dispatcher when a group of records changes", () => {
var pcd = new ProtoChangeDetector();
pcd.addAst(ast("1 + 2"), "memo", 1);
pcd.addAst(ast("10 + 20"), "memo", 1);
pcd.addAst(ast("100 + 200"), "memo2", 2);
var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, null);
cd.detectChanges();
expect(dispatcher.loggedValues).toEqual([[3, 30], [300]]);
});
it("should update every instance of a group individually", () => {
var pcd = new ProtoChangeDetector();
pcd.addAst(ast("1 + 2"), "memo", "memo");
var dispatcher = new TestDispatcher();
var cd = new DynamicChangeDetector(dispatcher, null, []);
cd.addChild(pcd.instantiate(dispatcher, null));
cd.addChild(pcd.instantiate(dispatcher, null));
cd.detectChanges();
expect(dispatcher.loggedValues).toEqual([[3], [3]]);
});
it("should notify the dispatcher before switching to the next group", () => {
var pcd = new ProtoChangeDetector();
pcd.addAst(ast("a()"), "a", 1);
pcd.addAst(ast("b()"), "b", 2);
pcd.addAst(ast("c()"), "c", 2);
var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, null);
var tr = new TestRecord();
tr.a = () => {dispatcher.logValue('InvokeA'); return 'a'};
tr.b = () => {dispatcher.logValue('InvokeB'); return 'b'};
tr.c = () => {dispatcher.logValue('InvokeC'); return 'c'};
cd.setContext(tr);
cd.detectChanges();
expect(dispatcher.loggedValues).toEqual(['InvokeA', ['a'], 'InvokeB', 'InvokeC', ['b', 'c']]);
});
});
describe("enforce no new changes", () => {
it("should throw when a record gets changed after it has been checked", () => {
var pcd = new ProtoChangeDetector();
pcd.addAst(ast("a"), "a", 1);
var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, null);
cd.setContext(new TestData('value'));
expect(() => {
cd.checkNoChanges();
}).toThrowError(new RegExp("Expression 'a in location' has changed after it was checked"));
});
});
describe("error handling", () => {
it("should wrap exceptions into ChangeDetectionError", () => {
var pcd = new ProtoChangeDetector();
pcd.addAst(ast('invalidProp', 'someComponent'), "a", 1);
var cd = pcd.instantiate(new TestDispatcher(), null);
cd.setContext(null);
try {
cd.detectChanges();
throw new BaseException("fail");
} catch (e) {
expect(e).toBeAnInstanceOf(ChangeDetectionError);
expect(e.location).toEqual("invalidProp in someComponent");
} }
});
});
describe("collections", () => { function createChangeDetector(memo:string, exp:string, context = null, formatters = null,
it("should support null values", () => { structural = false) {
var context = new TestData(null); var pcd = createProtoChangeDetector();
pcd.addAst(ast(exp), memo, memo, structural);
var c = createChangeDetector('a', 'a', context, null, true); var dispatcher = new TestDispatcher();
var cd = c["changeDetector"]; var cd = pcd.instantiate(dispatcher, formatters);
var dispatcher = c["dispatcher"]; cd.setContext(context);
cd.detectChanges(); return {"changeDetector" : cd, "dispatcher" : dispatcher};
expect(dispatcher.log).toEqual(['a=null']); }
dispatcher.clear();
//cd.detectChanges(); function executeWatch(memo:string, exp:string, context = null, formatters = null,
//expect(dispatcher.log).toEqual([]); content = false) {
var res = createChangeDetector(memo, exp, context, formatters, content);
res["changeDetector"].detectChanges();
return res["dispatcher"].log;
}
context.a = [0]; describe(`${name} change detection`, () => {
cd.detectChanges(); it('should do simple watching', () => {
var person = new Person("misko");
var c = createChangeDetector('name', 'name', person);
var cd = c["changeDetector"];
var dispatcher = c["dispatcher"];
expect(dispatcher.log).toEqual(["a=" + cd.detectChanges();
arrayChangesAsString({ expect(dispatcher.log).toEqual(['name=misko']);
collection: ['0[null->0]'], dispatcher.clear();
additions: ['0[null->0]']
})
]);
dispatcher.clear();
context.a = null; person.name = "Misko";
cd.detectChanges(); cd.detectChanges();
expect(dispatcher.log).toEqual(['a=null']); expect(dispatcher.log).toEqual(['name=Misko']);
}); });
describe("list", () => { it('should report all changes on the first run including uninitialized values', () => {
it("should support list changes", () => { expect(executeWatch('value', 'value', new Uninitialized())).toEqual(['value=null']);
var context = new TestData([1, 2]); });
expect(executeWatch("a", "a", context, null, true)) it('should report all changes on the first run including null values', () => {
.toEqual(["a=" + var td = new TestData(null);
arrayChangesAsString({ expect(executeWatch('a', 'a', td)).toEqual(['a=null']);
collection: ['1[null->0]', '2[null->1]'], });
additions: ['1[null->0]', '2[null->1]']
})]);
});
it("should handle reference changes", () => { it("should support literals", () => {
var context = new TestData([1, 2]); expect(executeWatch('const', '10')).toEqual(['const=10']);
var objs = createChangeDetector("a", "a", context, null, true); expect(executeWatch('const', '"str"')).toEqual(['const=str']);
var cd = objs["changeDetector"]; expect(executeWatch('const', '"a\n\nb"')).toEqual(['const=a\n\nb']);
var dispatcher = objs["dispatcher"]; });
cd.detectChanges();
dispatcher.clear();
context.a = [2, 1]; it('simple chained property access', () => {
cd.detectChanges(); var address = new Address('Grenoble');
expect(dispatcher.log).toEqual(["a=" + var person = new Person('Victor', address);
arrayChangesAsString({
collection: ['2[1->0]', '1[0->1]'],
previous: ['1[0->1]', '2[1->0]'],
moves: ['2[1->0]', '1[0->1]']
})]);
});
});
describe("map", () => { expect(executeWatch('address.city', 'address.city', person))
it("should support map changes", () => { .toEqual(['address.city=Grenoble']);
var map = MapWrapper.create(); });
MapWrapper.set(map, "foo", "bar");
var context = new TestData(map);
expect(executeWatch("a", "a", context, null, true))
.toEqual(["a=" +
kvChangesAsString({
map: ['foo[null->bar]'],
additions: ['foo[null->bar]']
})]);
});
it("should handle reference changes", () => { it("should support method calls", () => {
var map = MapWrapper.create(); var person = new Person('Victor');
MapWrapper.set(map, "foo", "bar"); expect(executeWatch('m', 'sayHi("Jim")', person)).toEqual(['m=Hi, Jim']);
var context = new TestData(map); });
var objs = createChangeDetector("a", "a", context, null, true);
var cd = objs["changeDetector"];
var dispatcher = objs["dispatcher"];
cd.detectChanges();
dispatcher.clear();
context.a = MapWrapper.create(); it("should support function calls", () => {
MapWrapper.set(context.a, "bar", "foo"); var td = new TestData(() => (a) => a);
cd.detectChanges(); expect(executeWatch('value', 'a()(99)', td)).toEqual(['value=99']);
expect(dispatcher.log).toEqual(["a=" + });
kvChangesAsString({
map: ['bar[null->foo]'],
previous: ['foo[bar->null]'],
additions: ['bar[null->foo]'],
removals: ['foo[bar->null]']
})]);
});
});
if (isJsObject({})) { it("should support chained method calls", () => {
describe("js objects", () => { var person = new Person('Victor');
it("should support object changes", () => { var td = new TestData(person);
var map = {"foo": "bar"}; expect(executeWatch('m', 'a.sayHi("Jim")', td)).toEqual(['m=Hi, Jim']);
var context = new TestData(map); });
expect(executeWatch("a", "a", context, null, true))
.toEqual(["a=" + it("should support literal array", () => {
kvChangesAsString({ var c = createChangeDetector('array', '[1,2]');
map: ['foo[null->bar]'], c["changeDetector"].detectChanges();
additions: ['foo[null->bar]'] expect(c["dispatcher"].loggedValues).toEqual([[[1, 2]]]);
})]);
c = createChangeDetector('array', '[1,a]', new TestData(2));
c["changeDetector"].detectChanges();
expect(c["dispatcher"].loggedValues).toEqual([[[1, 2]]]);
});
it("should support literal maps", () => {
var c = createChangeDetector('map', '{z:1}');
c["changeDetector"].detectChanges();
expect(c["dispatcher"].loggedValues[0][0]['z']).toEqual(1);
c = createChangeDetector('map', '{z:a}', new TestData(1));
c["changeDetector"].detectChanges();
expect(c["dispatcher"].loggedValues[0][0]['z']).toEqual(1);
});
it("should support binary operations", () => {
expect(executeWatch('exp', '10 + 2')).toEqual(['exp=12']);
expect(executeWatch('exp', '10 - 2')).toEqual(['exp=8']);
expect(executeWatch('exp', '10 * 2')).toEqual(['exp=20']);
expect(executeWatch('exp', '10 / 2')).toEqual([`exp=${5.0}`]); //dart exp=5.0, js exp=5
expect(executeWatch('exp', '11 % 2')).toEqual(['exp=1']);
expect(executeWatch('exp', '1 == 1')).toEqual(['exp=true']);
expect(executeWatch('exp', '1 != 1')).toEqual(['exp=false']);
expect(executeWatch('exp', '1 < 2')).toEqual(['exp=true']);
expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']);
expect(executeWatch('exp', '2 > 1')).toEqual(['exp=true']);
expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']);
expect(executeWatch('exp', '1 <= 2')).toEqual(['exp=true']);
expect(executeWatch('exp', '2 <= 2')).toEqual(['exp=true']);
expect(executeWatch('exp', '2 <= 1')).toEqual(['exp=false']);
expect(executeWatch('exp', '2 >= 1')).toEqual(['exp=true']);
expect(executeWatch('exp', '2 >= 2')).toEqual(['exp=true']);
expect(executeWatch('exp', '1 >= 2')).toEqual(['exp=false']);
expect(executeWatch('exp', 'true && true')).toEqual(['exp=true']);
expect(executeWatch('exp', 'true && false')).toEqual(['exp=false']);
expect(executeWatch('exp', 'true || false')).toEqual(['exp=true']);
expect(executeWatch('exp', 'false || false')).toEqual(['exp=false']);
});
it("should support negate", () => {
expect(executeWatch('exp', '!true')).toEqual(['exp=false']);
expect(executeWatch('exp', '!!true')).toEqual(['exp=true']);
});
it("should support conditionals", () => {
expect(executeWatch('m', '1 < 2 ? 1 : 2')).toEqual(['m=1']);
expect(executeWatch('m', '1 > 2 ? 1 : 2')).toEqual(['m=2']);
});
describe("keyed access", () => {
it("should support accessing a list item", () => {
expect(executeWatch('array[0]', '["foo", "bar"][0]')).toEqual(['array[0]=foo']);
});
it("should support accessing a map item", () => {
expect(executeWatch('map[foo]', '{"foo": "bar"}["foo"]')).toEqual(['map[foo]=bar']);
});
});
it("should support formatters", () => {
var formatters = MapWrapper.createFromPairs([
['uppercase', (v) => v.toUpperCase()],
['wrap', (v, before, after) => `${before}${v}${after}`]]);
expect(executeWatch('str', '"aBc" | uppercase', null, formatters)).toEqual(['str=ABC']);
expect(executeWatch('str', '"b" | wrap:"a":"c"', null, formatters)).toEqual(['str=abc']);
});
it("should support interpolation", () => {
var parser = new Parser(new Lexer());
var pcd = createProtoChangeDetector();
var ast = parser.parseInterpolation("B{{a}}A", "location");
pcd.addAst(ast, "memo", "memo", false);
var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, MapWrapper.create());
cd.setContext(new TestData("value"));
cd.detectChanges();
expect(dispatcher.log).toEqual(["memo=BvalueA"]);
});
describe("group changes", () => {
it("should notify the dispatcher when a group of records changes", () => {
var pcd = createProtoChangeDetector();
pcd.addAst(ast("1 + 2"), "memo", "1");
pcd.addAst(ast("10 + 20"), "memo", "1");
pcd.addAst(ast("100 + 200"), "memo2", "2");
var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, null);
cd.detectChanges();
expect(dispatcher.loggedValues).toEqual([[3, 30], [300]]);
});
it("should notify the dispatcher before switching to the next group", () => {
var pcd = createProtoChangeDetector();
pcd.addAst(ast("a()"), "a", "1");
pcd.addAst(ast("b()"), "b", "2");
pcd.addAst(ast("c()"), "c", "2");
var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, null);
var tr = new TestRecord();
tr.a = () => {
dispatcher.logValue('InvokeA');
return 'a'
};
tr.b = () => {
dispatcher.logValue('InvokeB');
return 'b'
};
tr.c = () => {
dispatcher.logValue('InvokeC');
return 'c'
};
cd.setContext(tr);
cd.detectChanges();
expect(dispatcher.loggedValues).toEqual(['InvokeA', ['a'], 'InvokeB', 'InvokeC', ['b', 'c']]);
});
});
describe("enforce no new changes", () => {
it("should throw when a record gets changed after it has been checked", () => {
var pcd = createProtoChangeDetector();
pcd.addAst(ast("a"), "a", 1);
var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, null);
cd.setContext(new TestData('value'));
expect(() => {
cd.checkNoChanges();
}).toThrowError(new RegExp("Expression 'a in location' has changed after it was checked"));
});
});
//TODO vsavkin: implement it
describe("error handling", () => {
xit("should wrap exceptions into ChangeDetectionError", () => {
var pcd = createProtoChangeDetector();
pcd.addAst(ast('invalidProp', 'someComponent'), "a", 1);
var cd = pcd.instantiate(new TestDispatcher(), null);
cd.setContext(null);
try {
cd.detectChanges();
throw new BaseException("fail");
} catch (e) {
expect(e).toBeAnInstanceOf(ChangeDetectionError);
expect(e.location).toEqual("invalidProp in someComponent");
}
});
});
describe("collections", () => {
it("should support null values", () => {
var context = new TestData(null);
var c = createChangeDetector('a', 'a', context, null, true);
var cd = c["changeDetector"];
var dispatcher = c["dispatcher"];
cd.detectChanges();
expect(dispatcher.log).toEqual(['a=null']);
dispatcher.clear();
context.a = [0];
cd.detectChanges();
expect(dispatcher.log).toEqual(["a=" +
arrayChangesAsString({
collection: ['0[null->0]'],
additions: ['0[null->0]']
})
]);
dispatcher.clear();
context.a = null;
cd.detectChanges();
expect(dispatcher.log).toEqual(['a=null']);
});
describe("list", () => {
it("should support list changes", () => {
var context = new TestData([1, 2]);
expect(executeWatch("a", "a", context, null, true))
.toEqual(["a=" +
arrayChangesAsString({
collection: ['1[null->0]', '2[null->1]'],
additions: ['1[null->0]', '2[null->1]']
})]);
});
it("should handle reference changes", () => {
var context = new TestData([1, 2]);
var objs = createChangeDetector("a", "a", context, null, true);
var cd = objs["changeDetector"];
var dispatcher = objs["dispatcher"];
cd.detectChanges();
dispatcher.clear();
context.a = [2, 1];
cd.detectChanges();
expect(dispatcher.log).toEqual(["a=" +
arrayChangesAsString({
collection: ['2[1->0]', '1[0->1]'],
previous: ['1[0->1]', '2[1->0]'],
moves: ['2[1->0]', '1[0->1]']
})]);
});
});
describe("map", () => {
it("should support map changes", () => {
var map = MapWrapper.create();
MapWrapper.set(map, "foo", "bar");
var context = new TestData(map);
expect(executeWatch("a", "a", context, null, true))
.toEqual(["a=" +
kvChangesAsString({
map: ['foo[null->bar]'],
additions: ['foo[null->bar]']
})]);
});
it("should handle reference changes", () => {
var map = MapWrapper.create();
MapWrapper.set(map, "foo", "bar");
var context = new TestData(map);
var objs = createChangeDetector("a", "a", context, null, true);
var cd = objs["changeDetector"];
var dispatcher = objs["dispatcher"];
cd.detectChanges();
dispatcher.clear();
context.a = MapWrapper.create();
MapWrapper.set(context.a, "bar", "foo");
cd.detectChanges();
expect(dispatcher.log).toEqual(["a=" +
kvChangesAsString({
map: ['bar[null->foo]'],
previous: ['foo[bar->null]'],
additions: ['bar[null->foo]'],
removals: ['foo[bar->null]']
})]);
});
});
if (!IS_DARTIUM) {
describe("js objects", () => {
it("should support object changes", () => {
var map = {"foo": "bar"};
var context = new TestData(map);
expect(executeWatch("a", "a", context, null, true))
.toEqual(["a=" +
kvChangesAsString({
map: ['foo[null->bar]'],
additions: ['foo[null->bar]']
})]);
});
});
}
});
describe("ContextWithVariableBindings", () => {
it('should read a field from ContextWithVariableBindings', () => {
var locals = new ContextWithVariableBindings(null,
MapWrapper.createFromPairs([["key", "value"]]));
expect(executeWatch('key', 'key', locals))
.toEqual(['key=value']);
});
it('should handle nested ContextWithVariableBindings', () => {
var nested = new ContextWithVariableBindings(null,
MapWrapper.createFromPairs([["key", "value"]]));
var locals = new ContextWithVariableBindings(nested, MapWrapper.create());
expect(executeWatch('key', 'key', locals))
.toEqual(['key=value']);
});
it("should fall back to a regular field read when ContextWithVariableBindings " +
"does not have the requested field", () => {
var locals = new ContextWithVariableBindings(new Person("Jim"),
MapWrapper.createFromPairs([["key", "value"]]));
expect(executeWatch('name', 'name', locals))
.toEqual(['name=Jim']);
});
});
describe("handle children", () => {
var parent, child;
beforeEach(() => {
var protoParent = createProtoChangeDetector();
parent = protoParent.instantiate(null, null);
var protoChild = createProtoChangeDetector();
child = protoChild.instantiate(null, null);
});
it("should add children", () => {
parent.addChild(child);
expect(parent.children.length).toEqual(1);
expect(parent.children[0]).toBe(child);
});
it("should remove children", () => {
parent.addChild(child);
parent.removeChild(child);
expect(parent.children).toEqual([]);
});
}); });
}); });
}
});
describe("ContextWithVariableBindings", () => {
it('should read a field from ContextWithVariableBindings', () => {
var locals = new ContextWithVariableBindings(null,
MapWrapper.createFromPairs([["key", "value"]]));
expect(executeWatch('key', 'key', locals))
.toEqual(['key=value']);
}); });
it('should handle nested ContextWithVariableBindings', () => {
var nested = new ContextWithVariableBindings(null,
MapWrapper.createFromPairs([["key", "value"]]));
var locals = new ContextWithVariableBindings(nested, MapWrapper.create());
expect(executeWatch('key', 'key', locals))
.toEqual(['key=value']);
});
it("should fall back to a regular field read when ContextWithVariableBindings " +
"does not have the requested field", () => {
var locals = new ContextWithVariableBindings(new Person("Jim"),
MapWrapper.createFromPairs([["key", "value"]]));
expect(executeWatch('name', 'name', locals))
.toEqual(['name=Jim']);
});
});
}); });
} }

View File

@ -4,7 +4,7 @@ import {DOM, Element} from 'facade/dom';
import {Compiler, CompilerCache} from './compiler/compiler'; import {Compiler, CompilerCache} from './compiler/compiler';
import {ProtoView} from './compiler/view'; import {ProtoView} from './compiler/view';
import {Reflector, reflector} from 'reflection/reflection'; import {Reflector, reflector} from 'reflection/reflection';
import {Parser, Lexer, ChangeDetector} from 'change_detection/change_detection'; import {Parser, Lexer, ChangeDetection, dynamicChangeDetection, jitChangeDetection} from 'change_detection/change_detection';
import {TemplateLoader} from './compiler/template_loader'; import {TemplateLoader} from './compiler/template_loader';
import {DirectiveMetadataReader} from './compiler/directive_metadata_reader'; import {DirectiveMetadataReader} from './compiler/directive_metadata_reader';
import {DirectiveMetadata} from './compiler/directive_metadata'; import {DirectiveMetadata} from './compiler/directive_metadata';
@ -17,7 +17,14 @@ var _rootInjector: Injector;
// Contains everything that is safe to share between applications. // Contains everything that is safe to share between applications.
var _rootBindings = [ var _rootBindings = [
bind(Reflector).toValue(reflector), Compiler, CompilerCache, TemplateLoader, DirectiveMetadataReader, Parser, Lexer bind(Reflector).toValue(reflector),
bind(ChangeDetection).toValue(dynamicChangeDetection),
Compiler,
CompilerCache,
TemplateLoader,
DirectiveMetadataReader,
Parser,
Lexer
]; ];
export var appViewToken = new OpaqueToken('AppView'); export var appViewToken = new OpaqueToken('AppView');
@ -45,20 +52,20 @@ function _injectorBindings(appComponentType) {
return element; return element;
}, [appComponentAnnotatedTypeToken, appDocumentToken]), }, [appComponentAnnotatedTypeToken, appDocumentToken]),
bind(appViewToken).toAsyncFactory((compiler, injector, appElement, bind(appViewToken).toAsyncFactory((changeDetection, compiler, injector, appElement,
appComponentAnnotatedType) => { appComponentAnnotatedType) => {
return compiler.compile(appComponentAnnotatedType.type, null).then( return compiler.compile(appComponentAnnotatedType.type, null).then(
(protoView) => { (protoView) => {
var appProtoView = ProtoView.createRootProtoView(protoView, var appProtoView = ProtoView.createRootProtoView(protoView,
appElement, appComponentAnnotatedType); appElement, appComponentAnnotatedType, changeDetection.createProtoChangeDetector('root'));
// The light Dom of the app element is not considered part of // The light Dom of the app element is not considered part of
// the angular application. Thus the context and lightDomInjector are // the angular application. Thus the context and lightDomInjector are
// empty. // empty.
var view = appProtoView.instantiate(null); var view = appProtoView.instantiate(null);
view.hydrate(injector, null, new Object()); view.hydrate(injector, null, new Object());
return view; return view;
}); });
}, [Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken]), }, [ChangeDetection, Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken]),
bind(appChangeDetectorToken).toFactory((rootView) => rootView.changeDetector, bind(appChangeDetectorToken).toFactory((rootView) => rootView.changeDetector,
[appViewToken]), [appViewToken]),

View File

@ -3,7 +3,7 @@ import {Promise, PromiseWrapper} from 'facade/async';
import {List, ListWrapper, MapWrapper} from 'facade/collection'; import {List, ListWrapper, MapWrapper} from 'facade/collection';
import {DOM, Element} from 'facade/dom'; import {DOM, Element} from 'facade/dom';
import {Parser} from 'change_detection/change_detection'; import {ChangeDetection, Parser} from 'change_detection/change_detection';
import {DirectiveMetadataReader} from './directive_metadata_reader'; import {DirectiveMetadataReader} from './directive_metadata_reader';
import {ProtoView} from './view'; import {ProtoView} from './view';
@ -52,7 +52,10 @@ export class Compiler {
_reader: DirectiveMetadataReader; _reader: DirectiveMetadataReader;
_parser:Parser; _parser:Parser;
_compilerCache:CompilerCache; _compilerCache:CompilerCache;
constructor(templateLoader:TemplateLoader, reader: DirectiveMetadataReader, parser:Parser, cache:CompilerCache) { _changeDetection:ChangeDetection;
constructor(changeDetection:ChangeDetection, templateLoader:TemplateLoader, reader: DirectiveMetadataReader, parser:Parser, cache:CompilerCache) {
this._changeDetection = changeDetection;
this._reader = reader; this._reader = reader;
this._parser = parser; this._parser = parser;
this._compilerCache = cache; this._compilerCache = cache;
@ -60,7 +63,7 @@ export class Compiler {
createSteps(component:DirectiveMetadata):List<CompileStep> { createSteps(component:DirectiveMetadata):List<CompileStep> {
var dirs = ListWrapper.map(component.componentDirectives, (d) => this._reader.read(d)); var dirs = ListWrapper.map(component.componentDirectives, (d) => this._reader.read(d));
return createDefaultSteps(this._parser, component, dirs); return createDefaultSteps(this._changeDetection, this._parser, component, dirs);
} }
compile(component:Type, templateRoot:Element = null):Promise<ProtoView> { compile(component:Type, templateRoot:Element = null):Promise<ProtoView> {

View File

@ -1,4 +1,4 @@
import {Parser} from 'change_detection/change_detection'; import {ChangeDetection, Parser} from 'change_detection/change_detection';
import {List} from 'facade/collection'; import {List} from 'facade/collection';
import {PropertyBindingParser} from './property_binding_parser'; import {PropertyBindingParser} from './property_binding_parser';
@ -17,8 +17,12 @@ import {stringify} from 'facade/lang';
* Takes in an HTMLElement and produces the ProtoViews, * Takes in an HTMLElement and produces the ProtoViews,
* ProtoElementInjectors and ElementBinders in the end. * ProtoElementInjectors and ElementBinders in the end.
*/ */
export function createDefaultSteps(parser:Parser, compiledComponent: DirectiveMetadata, export function createDefaultSteps(
changeDetection:ChangeDetection,
parser:Parser,
compiledComponent: DirectiveMetadata,
directives: List<DirectiveMetadata>) { directives: List<DirectiveMetadata>) {
var compilationUnit = stringify(compiledComponent.type); var compilationUnit = stringify(compiledComponent.type);
return [ return [
@ -27,7 +31,7 @@ export function createDefaultSteps(parser:Parser, compiledComponent: DirectiveMe
new DirectiveParser(directives), new DirectiveParser(directives),
new TextInterpolationParser(parser, compilationUnit), new TextInterpolationParser(parser, compilationUnit),
new ElementBindingMarker(), new ElementBindingMarker(),
new ProtoViewBuilder(), new ProtoViewBuilder(changeDetection),
new ProtoElementInjectorBuilder(), new ProtoElementInjectorBuilder(),
new ElementBinderBuilder() new ElementBinderBuilder()
]; ];

View File

@ -2,7 +2,7 @@ import {isPresent, BaseException} from 'facade/lang';
import {ListWrapper, MapWrapper} from 'facade/collection'; import {ListWrapper, MapWrapper} from 'facade/collection';
import {ProtoView} from '../view'; import {ProtoView} from '../view';
import {ProtoChangeDetector} from 'change_detection/change_detection'; import {ChangeDetection} from 'change_detection/change_detection';
import {CompileStep} from './compile_step'; import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
@ -18,10 +18,16 @@ import {CompileControl} from './compile_control';
* - CompileElement#isViewRoot * - CompileElement#isViewRoot
*/ */
export class ProtoViewBuilder extends CompileStep { export class ProtoViewBuilder extends CompileStep {
changeDetection:ChangeDetection;
constructor(changeDetection:ChangeDetection) {
this.changeDetection = changeDetection;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) { process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var inheritedProtoView = null; var inheritedProtoView = null;
if (current.isViewRoot) { if (current.isViewRoot) {
inheritedProtoView = new ProtoView(current.element, new ProtoChangeDetector()); var protoChangeDetector = this.changeDetection.createProtoChangeDetector('dummy');
inheritedProtoView = new ProtoView(current.element, protoChangeDetector);
if (isPresent(parent)) { if (isPresent(parent)) {
if (isPresent(parent.inheritedElementBinder.nestedProtoView)) { if (isPresent(parent.inheritedElementBinder.nestedProtoView)) {
throw new BaseException('Only one nested view per element is allowed'); throw new BaseException('Only one nested view per element is allowed');

View File

@ -492,9 +492,12 @@ export class ProtoView {
// and the component template is already compiled into protoView. // and the component template is already compiled into protoView.
// Used for bootstrapping. // Used for bootstrapping.
static createRootProtoView(protoView: ProtoView, static createRootProtoView(protoView: ProtoView,
insertionElement, rootComponentAnnotatedType: DirectiveMetadata): ProtoView { insertionElement, rootComponentAnnotatedType: DirectiveMetadata,
protoChangeDetector:ProtoChangeDetector
): ProtoView {
DOM.addClass(insertionElement, 'ng-binding'); DOM.addClass(insertionElement, 'ng-binding');
var rootProtoView = new ProtoView(insertionElement, new ProtoChangeDetector()); var rootProtoView = new ProtoView(insertionElement, protoChangeDetector);
rootProtoView.instantiateInPlace = true; rootProtoView.instantiateInPlace = true;
var binder = rootProtoView.bindElement( var binder = rootProtoView.bindElement(
new ProtoElementInjector(null, 0, [rootComponentAnnotatedType.type], true)); new ProtoElementInjector(null, 0, [rootComponentAnnotatedType.type], true));

View File

@ -12,7 +12,7 @@ import {CompileElement} from 'core/compiler/pipeline/compile_element';
import {CompileStep} from 'core/compiler/pipeline/compile_step' import {CompileStep} from 'core/compiler/pipeline/compile_step'
import {CompileControl} from 'core/compiler/pipeline/compile_control'; import {CompileControl} from 'core/compiler/pipeline/compile_control';
import {Lexer, Parser} from 'change_detection/change_detection'; import {Lexer, Parser, dynamicChangeDetection} from 'change_detection/change_detection';
export function main() { export function main() {
describe('compiler', function() { describe('compiler', function() {
@ -134,7 +134,7 @@ class RecursiveComponent {}
class TestableCompiler extends Compiler { class TestableCompiler extends Compiler {
steps:List; steps:List;
constructor(reader:DirectiveMetadataReader, steps:List<CompileStep>) { constructor(reader:DirectiveMetadataReader, steps:List<CompileStep>) {
super(null, reader, new Parser(new Lexer()), new CompilerCache()); super(dynamicChangeDetection, null, reader, new Parser(new Lexer()), new CompilerCache());
this.steps = steps; this.steps = steps;
} }
createSteps(component):List<CompileStep> { createSteps(component):List<CompileStep> {

View File

@ -3,7 +3,7 @@ import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_li
import {DOM} from 'facade/dom'; import {DOM} from 'facade/dom';
import {Injector} from 'di/di'; import {Injector} from 'di/di';
import {Lexer, Parser, ChangeDetector} from 'change_detection/change_detection'; import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/compiler/compiler'; import {Compiler, CompilerCache} from 'core/compiler/compiler';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader'; import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
@ -20,7 +20,8 @@ export function main() {
var compiler; var compiler;
beforeEach( () => { beforeEach( () => {
compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache()); compiler = new Compiler(dynamicChangeDetection, null, new DirectiveMetadataReader(),
new Parser(new Lexer()), new CompilerCache());
}); });
describe('react to record changes', function() { describe('react to record changes', function() {

View File

@ -16,7 +16,8 @@ import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'core/
import {ProtoElementInjector} from 'core/compiler/element_injector'; import {ProtoElementInjector} from 'core/compiler/element_injector';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader'; import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
import {ChangeDetector, Lexer, Parser, ProtoChangeDetector} from 'change_detection/change_detection'; import {ChangeDetector, Lexer, Parser, DynamicProtoChangeDetector,
} from 'change_detection/change_detection';
import {Injector} from 'di/di'; import {Injector} from 'di/di';
export function main() { export function main() {
@ -66,7 +67,7 @@ export function main() {
} }
if (isPresent(current.element.getAttribute('viewroot'))) { if (isPresent(current.element.getAttribute('viewroot'))) {
current.isViewRoot = true; current.isViewRoot = true;
current.inheritedProtoView = new ProtoView(current.element, new ProtoChangeDetector()); current.inheritedProtoView = new ProtoView(current.element, new DynamicProtoChangeDetector());
} else if (isPresent(parent)) { } else if (isPresent(parent)) {
current.inheritedProtoView = parent.inheritedProtoView; current.inheritedProtoView = parent.inheritedProtoView;
} }
@ -205,7 +206,7 @@ export function main() {
var results = pipeline.process(el('<div viewroot prop-binding directives></div>')); var results = pipeline.process(el('<div viewroot prop-binding directives></div>'));
var pv = results[0].inheritedProtoView; var pv = results[0].inheritedProtoView;
results[0].inheritedElementBinder.nestedProtoView = new ProtoView( results[0].inheritedElementBinder.nestedProtoView = new ProtoView(
el('<div></div>'), new ProtoChangeDetector()); el('<div></div>'), new DynamicProtoChangeDetector());
instantiateView(pv); instantiateView(pv);
evalContext.prop1 = 'a'; evalContext.prop1 = 'a';

View File

@ -1,5 +1,6 @@
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'test_lib/test_lib'; import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'test_lib/test_lib';
import {isPresent} from 'facade/lang'; import {isPresent} from 'facade/lang';
import {dynamicChangeDetection} from 'change_detection/change_detection';
import {ElementBinder} from 'core/compiler/element_binder'; import {ElementBinder} from 'core/compiler/element_binder';
import {ProtoViewBuilder} from 'core/compiler/pipeline/proto_view_builder'; import {ProtoViewBuilder} from 'core/compiler/pipeline/proto_view_builder';
import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline'; import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline';
@ -20,7 +21,7 @@ export function main() {
current.variableBindings = MapWrapper.createFromStringMap(variableBindings); current.variableBindings = MapWrapper.createFromStringMap(variableBindings);
} }
current.inheritedElementBinder = new ElementBinder(null, null, null); current.inheritedElementBinder = new ElementBinder(null, null, null);
}), new ProtoViewBuilder()]); }), new ProtoViewBuilder(dynamicChangeDetection)]);
} }
it('should not create a ProtoView when the isViewRoot flag is not set', () => { it('should not create a ProtoView when the isViewRoot flag is not set', () => {

View File

@ -3,7 +3,7 @@ import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_li
import {DOM} from 'facade/dom'; import {DOM} from 'facade/dom';
import {Injector} from 'di/di'; import {Injector} from 'di/di';
import {Lexer, Parser, ChangeDetector} from 'change_detection/change_detection'; import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/compiler/compiler'; import {Compiler, CompilerCache} from 'core/compiler/compiler';
import {LifeCycle} from 'core/life_cycle/life_cycle'; import {LifeCycle} from 'core/life_cycle/life_cycle';
@ -26,7 +26,8 @@ export function main() {
var compiler; var compiler;
beforeEach( () => { beforeEach( () => {
compiler = new Compiler(null, new TestDirectiveMetadataReader(strategy), compiler = new Compiler(dynamicChangeDetection, null,
new TestDirectiveMetadataReader(strategy),
new Parser(new Lexer()), new CompilerCache()); new Parser(new Lexer()), new CompilerCache());
}); });

View File

@ -5,7 +5,8 @@ import {ShadowDomEmulated} from 'core/compiler/shadow_dom';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader'; import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
import {Component, Decorator, Template} from 'core/annotations/annotations'; import {Component, Decorator, Template} from 'core/annotations/annotations';
import {OnChange} from 'core/core'; import {OnChange} from 'core/core';
import {Lexer, Parser, ProtoChangeDetector, ChangeDetector} from 'change_detection/change_detection'; import {Lexer, Parser, DynamicProtoChangeDetector,
ChangeDetector} from 'change_detection/change_detection';
import {TemplateConfig} from 'core/annotations/template_config'; import {TemplateConfig} from 'core/annotations/template_config';
import {EventEmitter} from 'core/annotations/events'; import {EventEmitter} from 'core/annotations/events';
import {List, MapWrapper} from 'facade/collection'; import {List, MapWrapper} from 'facade/collection';
@ -52,7 +53,7 @@ export function main() {
describe('instantiated from protoView', () => { describe('instantiated from protoView', () => {
var view; var view;
beforeEach(() => { beforeEach(() => {
var pv = new ProtoView(el('<div id="1"></div>'), new ProtoChangeDetector()); var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector());
view = pv.instantiate(null); view = pv.instantiate(null);
}); });
@ -73,7 +74,7 @@ export function main() {
describe('with locals', function() { describe('with locals', function() {
var view; var view;
beforeEach(() => { beforeEach(() => {
var pv = new ProtoView(el('<div id="1"></div>'), new ProtoChangeDetector()); var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector());
pv.bindVariable('context-foo', 'template-foo'); pv.bindVariable('context-foo', 'template-foo');
view = createView(pv); view = createView(pv);
}); });
@ -109,7 +110,7 @@ export function main() {
} }
it('should collect the root node in the ProtoView element', () => { it('should collect the root node in the ProtoView element', () => {
var pv = new ProtoView(templateAwareCreateElement('<div id="1"></div>'), new ProtoChangeDetector()); var pv = new ProtoView(templateAwareCreateElement('<div id="1"></div>'), new DynamicProtoChangeDetector());
var view = pv.instantiate(null); var view = pv.instantiate(null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
expect(view.nodes.length).toBe(1); expect(view.nodes.length).toBe(1);
@ -119,7 +120,7 @@ export function main() {
describe('collect elements with property bindings', () => { describe('collect elements with property bindings', () => {
it('should collect property bindings on the root element if it has the ng-binding class', () => { it('should collect property bindings on the root element if it has the ng-binding class', () => {
var pv = new ProtoView(templateAwareCreateElement('<div [prop]="a" class="ng-binding"></div>'), new ProtoChangeDetector()); var pv = new ProtoView(templateAwareCreateElement('<div [prop]="a" class="ng-binding"></div>'), new DynamicProtoChangeDetector());
pv.bindElement(null); pv.bindElement(null);
pv.bindElementProperty(parser.parseBinding('a', null), 'prop', reflector.setter('prop')); pv.bindElementProperty(parser.parseBinding('a', null), 'prop', reflector.setter('prop'));
@ -131,7 +132,7 @@ export function main() {
it('should collect property bindings on child elements with ng-binding class', () => { it('should collect property bindings on child elements with ng-binding class', () => {
var pv = new ProtoView(templateAwareCreateElement('<div><span></span><span class="ng-binding"></span></div>'), var pv = new ProtoView(templateAwareCreateElement('<div><span></span><span class="ng-binding"></span></div>'),
new ProtoChangeDetector()); new DynamicProtoChangeDetector());
pv.bindElement(null); pv.bindElement(null);
pv.bindElementProperty(parser.parseBinding('b', null), 'a', reflector.setter('a')); pv.bindElementProperty(parser.parseBinding('b', null), 'a', reflector.setter('a'));
@ -146,7 +147,7 @@ export function main() {
describe('collect text nodes with bindings', () => { describe('collect text nodes with bindings', () => {
it('should collect text nodes under the root element', () => { it('should collect text nodes under the root element', () => {
var pv = new ProtoView(templateAwareCreateElement('<div class="ng-binding">{{}}<span></span>{{}}</div>'), new ProtoChangeDetector()); var pv = new ProtoView(templateAwareCreateElement('<div class="ng-binding">{{}}<span></span>{{}}</div>'), new DynamicProtoChangeDetector());
pv.bindElement(null); pv.bindElement(null);
pv.bindTextNode(0, parser.parseBinding('a', null)); pv.bindTextNode(0, parser.parseBinding('a', null));
pv.bindTextNode(2, parser.parseBinding('b', null)); pv.bindTextNode(2, parser.parseBinding('b', null));
@ -160,7 +161,7 @@ export function main() {
it('should collect text nodes with bindings on child elements with ng-binding class', () => { it('should collect text nodes with bindings on child elements with ng-binding class', () => {
var pv = new ProtoView(templateAwareCreateElement('<div><span> </span><span class="ng-binding">{{}}</span></div>'), var pv = new ProtoView(templateAwareCreateElement('<div><span> </span><span class="ng-binding">{{}}</span></div>'),
new ProtoChangeDetector()); new DynamicProtoChangeDetector());
pv.bindElement(null); pv.bindElement(null);
pv.bindTextNode(0, parser.parseBinding('b', null)); pv.bindTextNode(0, parser.parseBinding('b', null));
@ -176,7 +177,7 @@ export function main() {
describe('inplace instantiation', () => { describe('inplace instantiation', () => {
it('should be supported.', () => { it('should be supported.', () => {
var template = el('<div></div>'); var template = el('<div></div>');
var pv = new ProtoView(template, new ProtoChangeDetector()); var pv = new ProtoView(template, new DynamicProtoChangeDetector());
pv.instantiateInPlace = true; pv.instantiateInPlace = true;
var view = pv.instantiate(null); var view = pv.instantiate(null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
@ -185,7 +186,7 @@ export function main() {
it('should be off by default.', () => { it('should be off by default.', () => {
var template = el('<div></div>') var template = el('<div></div>')
var view = new ProtoView(template, new ProtoChangeDetector()) var view = new ProtoView(template, new DynamicProtoChangeDetector())
.instantiate(null); .instantiate(null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
expect(view.nodes[0]).not.toBe(template); expect(view.nodes[0]).not.toBe(template);
@ -202,7 +203,7 @@ export function main() {
describe('create ElementInjectors', () => { describe('create ElementInjectors', () => {
it('should use the directives of the ProtoElementInjector', () => { it('should use the directives of the ProtoElementInjector', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), new ProtoChangeDetector()); var pv = new ProtoView(el('<div class="ng-binding"></div>'), new DynamicProtoChangeDetector());
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
var view = pv.instantiate(null); var view = pv.instantiate(null);
@ -213,7 +214,7 @@ export function main() {
it('should use the correct parent', () => { it('should use the correct parent', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'), var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new ProtoChangeDetector()); new DynamicProtoChangeDetector());
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]); var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
pv.bindElement(protoParent); pv.bindElement(protoParent);
pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective])); pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective]));
@ -227,7 +228,7 @@ export function main() {
it('should not pass the host injector when a parent injector exists', () => { it('should not pass the host injector when a parent injector exists', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'), var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new ProtoChangeDetector()); new DynamicProtoChangeDetector());
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]); var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
pv.bindElement(protoParent); pv.bindElement(protoParent);
var testProtoElementInjector = new TestProtoElementInjector(protoParent, 1, [AnotherDirective]); var testProtoElementInjector = new TestProtoElementInjector(protoParent, 1, [AnotherDirective]);
@ -243,7 +244,7 @@ export function main() {
it('should pass the host injector when there is no parent injector', () => { it('should pass the host injector when there is no parent injector', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'), var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new ProtoChangeDetector()); new DynamicProtoChangeDetector());
pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective])); pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective]));
var testProtoElementInjector = new TestProtoElementInjector(null, 1, [AnotherDirective]); var testProtoElementInjector = new TestProtoElementInjector(null, 1, [AnotherDirective]);
pv.bindElement(testProtoElementInjector); pv.bindElement(testProtoElementInjector);
@ -260,7 +261,7 @@ export function main() {
it('should collect a single root element injector', () => { it('should collect a single root element injector', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'), var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new ProtoChangeDetector()); new DynamicProtoChangeDetector());
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]); var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
pv.bindElement(protoParent); pv.bindElement(protoParent);
pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective])); pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective]));
@ -273,7 +274,7 @@ export function main() {
it('should collect multiple root element injectors', () => { it('should collect multiple root element injectors', () => {
var pv = new ProtoView(el('<div><span class="ng-binding"></span><span class="ng-binding"></span></div>'), var pv = new ProtoView(el('<div><span class="ng-binding"></span><span class="ng-binding"></span></div>'),
new ProtoChangeDetector()); new DynamicProtoChangeDetector());
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
pv.bindElement(new ProtoElementInjector(null, 2, [AnotherDirective])); pv.bindElement(new ProtoElementInjector(null, 2, [AnotherDirective]));
@ -290,7 +291,7 @@ export function main() {
var ctx; var ctx;
function createComponentWithSubPV(subProtoView) { function createComponentWithSubPV(subProtoView) {
var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'), new ProtoChangeDetector()); var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'), new DynamicProtoChangeDetector());
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponent], true)); var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponent], true));
binder.componentDirective = someComponentDirective; binder.componentDirective = someComponentDirective;
binder.nestedProtoView = subProtoView; binder.nestedProtoView = subProtoView;
@ -305,7 +306,7 @@ export function main() {
} }
it('should expose component services to the component', () => { it('should expose component services to the component', () => {
var subpv = new ProtoView(el('<span></span>'), new ProtoChangeDetector()); var subpv = new ProtoView(el('<span></span>'), new DynamicProtoChangeDetector());
var pv = createComponentWithSubPV(subpv); var pv = createComponentWithSubPV(subpv);
var view = createNestedView(pv); var view = createNestedView(pv);
@ -317,7 +318,7 @@ export function main() {
it('should expose component services and component instance to directives in the shadow Dom', it('should expose component services and component instance to directives in the shadow Dom',
() => { () => {
var subpv = new ProtoView( var subpv = new ProtoView(
el('<div dec class="ng-binding">hello shadow dom</div>'), new ProtoChangeDetector()); el('<div dec class="ng-binding">hello shadow dom</div>'), new DynamicProtoChangeDetector());
subpv.bindElement( subpv.bindElement(
new ProtoElementInjector(null, 0, [ServiceDependentDecorator])); new ProtoElementInjector(null, 0, [ServiceDependentDecorator]));
var pv = createComponentWithSubPV(subpv); var pv = createComponentWithSubPV(subpv);
@ -340,7 +341,7 @@ export function main() {
it('dehydration should dehydrate child component views too', () => { it('dehydration should dehydrate child component views too', () => {
var subpv = new ProtoView( var subpv = new ProtoView(
el('<div dec class="ng-binding">hello shadow dom</div>'), new ProtoChangeDetector()); el('<div dec class="ng-binding">hello shadow dom</div>'), new DynamicProtoChangeDetector());
subpv.bindElement( subpv.bindElement(
new ProtoElementInjector(null, 0, [ServiceDependentDecorator])); new ProtoElementInjector(null, 0, [ServiceDependentDecorator]));
var pv = createComponentWithSubPV(subpv); var pv = createComponentWithSubPV(subpv);
@ -355,7 +356,7 @@ export function main() {
}); });
it('should create shadow dom', () => { it('should create shadow dom', () => {
var subpv = new ProtoView(el('<span>hello shadow dom</span>'), new ProtoChangeDetector()); var subpv = new ProtoView(el('<span>hello shadow dom</span>'), new DynamicProtoChangeDetector());
var pv = createComponentWithSubPV(subpv); var pv = createComponentWithSubPV(subpv);
var view = createNestedView(pv); var view = createNestedView(pv);
@ -364,9 +365,9 @@ export function main() {
}); });
it('should use the provided shadow DOM strategy', () => { it('should use the provided shadow DOM strategy', () => {
var subpv = new ProtoView(el('<span>hello shadow dom</span>'), new ProtoChangeDetector()); var subpv = new ProtoView(el('<span>hello shadow dom</span>'), new DynamicProtoChangeDetector());
var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'), new ProtoChangeDetector()); var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'), new DynamicProtoChangeDetector());
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponentWithEmulatedShadowDom], true)); var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponentWithEmulatedShadowDom], true));
binder.componentDirective = new DirectiveMetadataReader().read(SomeComponentWithEmulatedShadowDom); binder.componentDirective = new DirectiveMetadataReader().read(SomeComponentWithEmulatedShadowDom);
binder.nestedProtoView = subpv; binder.nestedProtoView = subpv;
@ -380,8 +381,8 @@ export function main() {
describe('with template views', () => { describe('with template views', () => {
function createViewWithTemplate() { function createViewWithTemplate() {
var templateProtoView = new ProtoView( var templateProtoView = new ProtoView(
el('<div id="1"></div>'), new ProtoChangeDetector()); el('<div id="1"></div>'), new DynamicProtoChangeDetector());
var pv = new ProtoView(el('<someTmpl class="ng-binding"></someTmpl>'), new ProtoChangeDetector()); var pv = new ProtoView(el('<someTmpl class="ng-binding"></someTmpl>'), new DynamicProtoChangeDetector());
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeTemplate])); var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeTemplate]));
binder.templateDirective = someTemplateDirective; binder.templateDirective = someTemplateDirective;
binder.nestedProtoView = templateProtoView; binder.nestedProtoView = templateProtoView;
@ -425,7 +426,7 @@ export function main() {
function createProtoView() { function createProtoView() {
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'), var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'),
new ProtoChangeDetector()); new DynamicProtoChangeDetector());
pv.bindElement(new TestProtoElementInjector(null, 0, [])); pv.bindElement(new TestProtoElementInjector(null, 0, []));
pv.bindEvent('click', parser.parseBinding('callMe(\$event)', null)); pv.bindEvent('click', parser.parseBinding('callMe(\$event)', null));
return pv; return pv;
@ -460,7 +461,7 @@ export function main() {
it('should support custom event emitters', () => { it('should support custom event emitters', () => {
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'), var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'),
new ProtoChangeDetector()); new DynamicProtoChangeDetector());
pv.bindElement(new TestProtoElementInjector(null, 0, [EventEmitterDirective])); pv.bindElement(new TestProtoElementInjector(null, 0, [EventEmitterDirective]));
pv.bindEvent('click', parser.parseBinding('callMe(\$event)', null)); pv.bindEvent('click', parser.parseBinding('callMe(\$event)', null));
@ -491,7 +492,7 @@ export function main() {
it('should consume text node changes', () => { it('should consume text node changes', () => {
var pv = new ProtoView(el('<div class="ng-binding">{{}}</div>'), var pv = new ProtoView(el('<div class="ng-binding">{{}}</div>'),
new ProtoChangeDetector()); new DynamicProtoChangeDetector());
pv.bindElement(null); pv.bindElement(null);
pv.bindTextNode(0, parser.parseBinding('foo', null)); pv.bindTextNode(0, parser.parseBinding('foo', null));
createViewAndChangeDetector(pv); createViewAndChangeDetector(pv);
@ -503,7 +504,7 @@ export function main() {
it('should consume element binding changes', () => { it('should consume element binding changes', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(el('<div class="ng-binding"></div>'),
new ProtoChangeDetector()); new DynamicProtoChangeDetector());
pv.bindElement(null); pv.bindElement(null);
pv.bindElementProperty(parser.parseBinding('foo', null), 'id', reflector.setter('id')); pv.bindElementProperty(parser.parseBinding('foo', null), 'id', reflector.setter('id'));
createViewAndChangeDetector(pv); createViewAndChangeDetector(pv);
@ -515,7 +516,7 @@ export function main() {
it('should consume directive watch expression change', () => { it('should consume directive watch expression change', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(el('<div class="ng-binding"></div>'),
new ProtoChangeDetector()); new DynamicProtoChangeDetector());
pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective])); pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective]));
pv.bindDirectiveProperty(0, parser.parseBinding('foo', null), 'prop', reflector.setter('prop'), false); pv.bindDirectiveProperty(0, parser.parseBinding('foo', null), 'prop', reflector.setter('prop'), false);
createViewAndChangeDetector(pv); createViewAndChangeDetector(pv);
@ -527,7 +528,7 @@ export function main() {
it('should notify a directive about changes after all its properties have been set', () => { it('should notify a directive about changes after all its properties have been set', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(el('<div class="ng-binding"></div>'),
new ProtoChangeDetector()); new DynamicProtoChangeDetector());
pv.bindElement(new ProtoElementInjector(null, 0, [DirectiveImplementingOnChange])); pv.bindElement(new ProtoElementInjector(null, 0, [DirectiveImplementingOnChange]));
pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'), false); pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'), false);
@ -544,7 +545,7 @@ export function main() {
it('should provide a map of updated properties', () => { it('should provide a map of updated properties', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(el('<div class="ng-binding"></div>'),
new ProtoChangeDetector()); new DynamicProtoChangeDetector());
pv.bindElement(new ProtoElementInjector(null, 0, [DirectiveImplementingOnChange])); pv.bindElement(new ProtoElementInjector(null, 0, [DirectiveImplementingOnChange]));
pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'), false); pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'), false);
@ -569,18 +570,20 @@ export function main() {
var element, pv; var element, pv;
beforeEach(() => { beforeEach(() => {
element = DOM.createElement('div'); element = DOM.createElement('div');
pv = new ProtoView(el('<div>hi</div>'), new ProtoChangeDetector()); pv = new ProtoView(el('<div>hi</div>'), new DynamicProtoChangeDetector());
}); });
it('should create the root component when instantiated', () => { it('should create the root component when instantiated', () => {
var rootProtoView = ProtoView.createRootProtoView(pv, element, someComponentDirective); var rootProtoView = ProtoView.createRootProtoView(pv, element,
someComponentDirective, new DynamicProtoChangeDetector());
var view = rootProtoView.instantiate(null); var view = rootProtoView.instantiate(null);
view.hydrate(new Injector([]), null, null); view.hydrate(new Injector([]), null, null);
expect(view.rootElementInjectors[0].get(SomeComponent)).not.toBe(null); expect(view.rootElementInjectors[0].get(SomeComponent)).not.toBe(null);
}); });
it('should inject the protoView into the shadowDom', () => { it('should inject the protoView into the shadowDom', () => {
var rootProtoView = ProtoView.createRootProtoView(pv, element, someComponentDirective); var rootProtoView = ProtoView.createRootProtoView(pv, element,
someComponentDirective, new DynamicProtoChangeDetector());
var view = rootProtoView.instantiate(null); var view = rootProtoView.instantiate(null);
view.hydrate(new Injector([]), null, null); view.hydrate(new Injector([]), null, null);
expect(element.shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hi'); expect(element.shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hi');

View File

@ -6,10 +6,10 @@ import {DOM} from 'facade/dom';
import {ListWrapper, MapWrapper} from 'facade/collection'; import {ListWrapper, MapWrapper} from 'facade/collection';
import {Injector} from 'di/di'; import {Injector} from 'di/di';
import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector'; import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector';
import {ProtoChangeDetector, ChangeDetector, Lexer, Parser} from 'change_detection/change_detection'; import {DynamicProtoChangeDetector, ChangeDetector, Lexer, Parser} from 'change_detection/change_detection';
function createView(nodes) { function createView(nodes) {
var view = new View(null, nodes, new ProtoChangeDetector(), MapWrapper.create()); var view = new View(null, nodes, new DynamicProtoChangeDetector(), MapWrapper.create());
view.init([], [], [], [], [], [], []); view.init([], [], [], [], [], [], []);
return view; return view;
} }
@ -68,7 +68,7 @@ export function main() {
dom = el(`<div><stuff></stuff><div insert-after-me></div><stuff></stuff></div>`); dom = el(`<div><stuff></stuff><div insert-after-me></div><stuff></stuff></div>`);
var insertionElement = dom.childNodes[1]; var insertionElement = dom.childNodes[1];
parentView = createView([dom.childNodes[0]]); parentView = createView([dom.childNodes[0]]);
protoView = new ProtoView(el('<div>hi</div>'), new ProtoChangeDetector()); protoView = new ProtoView(el('<div>hi</div>'), new DynamicProtoChangeDetector());
elementInjector = new ElementInjector(null, null, null, null); elementInjector = new ElementInjector(null, null, null, null);
viewPort = new ViewPort(parentView, insertionElement, protoView, elementInjector); viewPort = new ViewPort(parentView, insertionElement, protoView, elementInjector);
customViewWithOneNode = createView([el('<div>single</div>')]); customViewWithOneNode = createView([el('<div>single</div>')]);
@ -165,7 +165,7 @@ export function main() {
expect(textInViewPort()).toEqual('filler one two'); expect(textInViewPort()).toEqual('filler one two');
expect(viewPort.length).toBe(2); expect(viewPort.length).toBe(2);
}); });
it('should keep views hydration state during insert', () => { it('should keep views hydration state during insert', () => {
var hydratedView = new HydrateAwareFakeView(true); var hydratedView = new HydrateAwareFakeView(true);
var dehydratedView = new HydrateAwareFakeView(false); var dehydratedView = new HydrateAwareFakeView(false);
@ -212,7 +212,7 @@ export function main() {
viewPort.hydrate(new Injector([]), null); viewPort.hydrate(new Injector([]), null);
var pv = new ProtoView(el('<div class="ng-binding">{{}}</div>'), var pv = new ProtoView(el('<div class="ng-binding">{{}}</div>'),
new ProtoChangeDetector()); new DynamicProtoChangeDetector());
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
pv.bindTextNode(0, parser.parseBinding('foo', null)); pv.bindTextNode(0, parser.parseBinding('foo', null));
fancyView = pv.instantiate(null); fancyView = pv.instantiate(null);

View File

@ -3,7 +3,7 @@ import {describe, xit, it, expect, beforeEach, ddescribe, iit, IS_DARTIUM, el} f
import {DOM} from 'facade/dom'; import {DOM} from 'facade/dom';
import {Injector} from 'di/di'; import {Injector} from 'di/di';
import {Lexer, Parser, ChangeDetector} from 'change_detection/change_detection'; import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/compiler/compiler'; import {Compiler, CompilerCache} from 'core/compiler/compiler';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader'; import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
@ -17,7 +17,8 @@ export function main() {
describe('ng-if', () => { describe('ng-if', () => {
var view, cd, compiler, component; var view, cd, compiler, component;
beforeEach(() => { beforeEach(() => {
compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache()); compiler = new Compiler(dynamicChangeDetection, null, new DirectiveMetadataReader(),
new Parser(new Lexer()), new CompilerCache());
}); });
function createView(pv) { function createView(pv) {

View File

@ -1,7 +1,7 @@
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_lib/test_lib'; import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_lib/test_lib';
import {DOM} from 'facade/dom'; import {DOM} from 'facade/dom';
import {Injector} from 'di/di'; import {Injector} from 'di/di';
import {Lexer, Parser, ChangeDetector} from 'change_detection/change_detection'; import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/compiler/compiler'; import {Compiler, CompilerCache} from 'core/compiler/compiler';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader'; import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
import {Decorator, Component} from 'core/annotations/annotations'; import {Decorator, Component} from 'core/annotations/annotations';
@ -13,7 +13,8 @@ export function main() {
describe('ng-non-bindable', () => { describe('ng-non-bindable', () => {
var view, cd, compiler, component; var view, cd, compiler, component;
beforeEach(() => { beforeEach(() => {
compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache()); compiler = new Compiler(dynamicChangeDetection,
null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache());
}); });
function createView(pv) { function createView(pv) {

View File

@ -3,7 +3,7 @@ import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_li
import {DOM} from 'facade/dom'; import {DOM} from 'facade/dom';
import {Injector} from 'di/di'; import {Injector} from 'di/di';
import {Lexer, Parser} from 'change_detection/change_detection'; import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/compiler/compiler'; import {Compiler, CompilerCache} from 'core/compiler/compiler';
import {OnChange} from 'core/compiler/interfaces'; import {OnChange} from 'core/compiler/interfaces';
@ -20,7 +20,8 @@ export function main() {
describe('ng-repeat', () => { describe('ng-repeat', () => {
var view, cd, compiler, component; var view, cd, compiler, component;
beforeEach(() => { beforeEach(() => {
compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache()); compiler = new Compiler(dynamicChangeDetection, null, new DirectiveMetadataReader(),
new Parser(new Lexer()), new CompilerCache());
}); });
function createView(pv) { function createView(pv) {

View File

@ -1,7 +1,7 @@
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_lib/test_lib'; import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_lib/test_lib';
import {DOM} from 'facade/dom'; import {DOM} from 'facade/dom';
import {Injector} from 'di/di'; import {Injector} from 'di/di';
import {Lexer, Parser} from 'change_detection/change_detection'; import {Lexer, Parser, dynamicChangeDetection} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/compiler/compiler'; import {Compiler, CompilerCache} from 'core/compiler/compiler';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader'; import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
import {Component} from 'core/annotations/annotations'; import {Component} from 'core/annotations/annotations';
@ -12,7 +12,8 @@ export function main() {
describe('ng-switch', () => { describe('ng-switch', () => {
var view, cd, compiler, component; var view, cd, compiler, component;
beforeEach(() => { beforeEach(() => {
compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache()); compiler = new Compiler(dynamicChangeDetection, null, new DirectiveMetadataReader(),
new Parser(new Lexer()), new CompilerCache());
}); });
function createView(pv) { function createView(pv) {

View File

@ -1,7 +1,7 @@
import * as app from './index_common'; import * as app from './index_common';
import {Component, Decorator, TemplateConfig, NgElement} from 'angular/angular'; import {Component, Decorator, TemplateConfig, NgElement} from 'angular/angular';
import {Lexer, Parser, ChangeDetector} from 'change_detection/change_detection'; import {Lexer, Parser, ChangeDetection, ChangeDetector} from 'change_detection/change_detection';
import {LifeCycle} from 'core/life_cycle/life_cycle'; import {LifeCycle} from 'core/life_cycle/life_cycle';
import {Compiler, CompilerCache} from 'core/compiler/compiler'; import {Compiler, CompilerCache} from 'core/compiler/compiler';
@ -37,8 +37,8 @@ function setup() {
}); });
reflector.registerType(Compiler, { reflector.registerType(Compiler, {
"factory": (templateLoader, reader, parser, compilerCache) => new Compiler(templateLoader, reader, parser, compilerCache), "factory": (changeDetection, templateLoader, reader, parser, compilerCache) => new Compiler(changeDetection, templateLoader, reader, parser, compilerCache),
"parameters": [[TemplateLoader], [DirectiveMetadataReader], [Parser], [CompilerCache]], "parameters": [[ChangeDetection], [TemplateLoader], [DirectiveMetadataReader], [Parser], [CompilerCache]],
"annotations": [] "annotations": []
}); });

View File

@ -89,8 +89,8 @@ class ListWrapper {
static reduce(List list, Function fn, init) { static reduce(List list, Function fn, init) {
return list.fold(init, fn); return list.fold(init, fn);
} }
static first(List list) => list.first; static first(List list) => list.isEmpty ? null : list.first;
static last(List list) => list.last; static last(List list) => list.isEmpty ? null : list.last;
static List reversed(List list) => list.reversed.toList(); static List reversed(List list) => list.reversed.toList();
static void push(List l, e) { l.add(e); } static void push(List l, e) { l.add(e); }
static List concat(List a, List b) {a.addAll(b); return a;} static List concat(List a, List b) {a.addAll(b); return a;}

View File

@ -27,6 +27,7 @@ class IMPLEMENTS {
bool isPresent(obj) => obj != null; bool isPresent(obj) => obj != null;
bool isBlank(obj) => obj == null; bool isBlank(obj) => obj == null;
bool isString(obj) => obj is String;
String stringify(obj) => obj.toString(); String stringify(obj) => obj.toString();

View File

@ -27,6 +27,10 @@ export function isBlank(obj):boolean {
return obj === undefined || obj === null; return obj === undefined || obj === null;
} }
export function isString(obj):boolean {
return typeof obj === "string";
}
export function stringify(token):string { export function stringify(token):string {
if (typeof token === 'string') { if (typeof token === 'string') {
return token; return token;