feat(change_detection): added a directive lifecycle hook that is called after children are checked

This commit is contained in:
vsavkin
2015-03-26 14:36:25 -07:00
parent 507f7ea70a
commit 723e8fde93
13 changed files with 283 additions and 82 deletions

View File

@ -42,11 +42,13 @@ export class AbstractChangeDetector extends ChangeDetector {
this.detectChangesInRecords(throwOnChange);
this._detectChangesInChildren(throwOnChange);
this.notifyOnAllChangesDone();
if (this.mode === CHECK_ONCE) this.mode = CHECKED;
}
detectChangesInRecords(throwOnChange:boolean){}
notifyOnAllChangesDone(){}
_detectChangesInChildren(throwOnChange:boolean) {
var children = this.children;

View File

@ -1,7 +1,7 @@
library change_detectoin.change_detection_jit_generator;
class ChangeDetectorJITGenerator {
ChangeDetectorJITGenerator(typeName, records) {
ChangeDetectorJITGenerator(typeName, records, directiveMementos) {
}
generate() {

View File

@ -64,6 +64,7 @@ import {
* }
* }
*
* ChangeDetector0.prototype.notifyOnAllChangesDone = function() {}
*
* ChangeDetector0.prototype.hydrate = function(context, locals) {
* this.context = context;
@ -96,31 +97,35 @@ var UTIL = "ChangeDetectionUtil";
var DISPATCHER_ACCESSOR = "this.dispatcher";
var PIPE_REGISTRY_ACCESSOR = "this.pipeRegistry";
var PROTOS_ACCESSOR = "this.protos";
var MEMENTOS_ACCESSOR = "this.directiveMementos";
var CONTEXT_ACCESSOR = "this.context";
var CHANGE_LOCAL = "change";
var CHANGES_LOCAL = "changes";
var LOCALS_ACCESSOR = "this.locals";
var TEMP_LOCAL = "temp";
function typeTemplate(type:string, cons:string, detectChanges:string, setContext:string):string {
function typeTemplate(type:string, cons:string, detectChanges:string,
notifyOnAllChangesDone:string, setContext:string):string {
return `
${cons}
${detectChanges}
${notifyOnAllChangesDone}
${setContext};
return function(dispatcher, pipeRegistry) {
return new ${type}(dispatcher, pipeRegistry, protos);
return new ${type}(dispatcher, pipeRegistry, protos, directiveMementos);
}
`;
}
function constructorTemplate(type:string, fieldsDefinitions:string):string {
return `
var ${type} = function ${type}(dispatcher, pipeRegistry, protos) {
var ${type} = function ${type}(dispatcher, pipeRegistry, protos, directiveMementos) {
${ABSTRACT_CHANGE_DETECTOR}.call(this);
${DISPATCHER_ACCESSOR} = dispatcher;
${PIPE_REGISTRY_ACCESSOR} = pipeRegistry;
${PROTOS_ACCESSOR} = protos;
${MEMENTOS_ACCESSOR} = directiveMementos;
${fieldsDefinitions}
}
@ -157,6 +162,18 @@ ${type}.prototype.detectChangesInRecords = function(throwOnChange) {
`;
}
function notifyOnAllChangesDoneTemplate(type:string, body:string):string {
return `
${type}.prototype.notifyOnAllChangesDone = function() {
${body}
}
`;
}
function onAllChangesDoneTemplate(index:number):string {
return `${DISPATCHER_ACCESSOR}.onAllChangesDone(${MEMENTOS_ACCESSOR}[${index}]);`;
}
function bodyTemplate(localDefinitions:string, changeDefinitions:string, records:string):string {
return `
@ -247,14 +264,16 @@ function addSimpleChangeRecordTemplate(protoIndex:number, oldValue:string, newVa
export class ChangeDetectorJITGenerator {
typeName:string;
records:List<ProtoRecord>;
directiveMementos:List;
localNames:List<String>;
changeNames:List<String>;
fieldNames:List<String>;
pipeNames:List<String>;
constructor(typeName:string, records:List<ProtoRecord>) {
constructor(typeName:string, records:List<ProtoRecord>, directiveMementos:List) {
this.typeName = typeName;
this.records = records;
this.directiveMementos = directiveMementos;
this.localNames = this.getLocalNames(records);
this.changeNames = this.getChangeNames(this.localNames);
@ -284,8 +303,10 @@ export class ChangeDetectorJITGenerator {
}
generate():Function {
var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(), this.genHydrate());
return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'protos', text)(AbstractChangeDetector, ChangeDetectionUtil, this.records);
var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(),
this.genNotifyOnAllChangesDone(), this.genHydrate());
return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'protos', 'directiveMementos', text)
(AbstractChangeDetector, ChangeDetectionUtil, this.records, this.directiveMementos);
}
genConstructor():string {
@ -319,6 +340,20 @@ export class ChangeDetectorJITGenerator {
return detectChangesTemplate(this.typeName, body);
}
genNotifyOnAllChangesDone():string {
var notifications = [];
var mementos = this.directiveMementos;
for (var i = mementos.length - 1; i >= 0; --i) {
var memento = mementos[i];
if (memento.notifyOnAllChangesDone) {
notifications.push(onAllChangesDoneTemplate(i));
}
}
return notifyOnAllChangesDoneTemplate(this.typeName, notifications.join(";\n"));
}
genBody():string {
var rec = this.records.map((r) => this.genRecord(r)).join("\n");
return bodyTemplate(this.genLocalDefinitions(), this.genChangeDefinitions(), rec);

View File

@ -34,8 +34,9 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
prevContexts:List;
protos:List<ProtoRecord>;
directiveMementos:List;
constructor(dispatcher:any, pipeRegistry:PipeRegistry, protoRecords:List<ProtoRecord>) {
constructor(dispatcher:any, pipeRegistry:PipeRegistry, protoRecords:List<ProtoRecord>, directiveMementos:List) {
super();
this.dispatcher = dispatcher;
this.pipeRegistry = pipeRegistry;
@ -52,6 +53,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
this.locals = null;
this.protos = protoRecords;
this.directiveMementos = directiveMementos;
}
hydrate(context:any, locals:any) {
@ -102,6 +104,16 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
}
}
notifyOnAllChangesDone() {
var mementos = this.directiveMementos;
for (var i = mementos.length - 1; i >= 0; --i) {
var memento = mementos[i];
if (memento.notifyOnAllChangesDone) {
this.dispatcher.onAllChangesDone(memento);
}
}
}
_check(proto:ProtoRecord) {
try {
if (proto.mode === RECORD_TYPE_PIPE || proto.mode === RECORD_TYPE_BINDING_PIPE) {

View File

@ -47,7 +47,7 @@ import {
export class ProtoChangeDetector {
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null){}
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List):ChangeDetector{
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List, directiveMemento:List):ChangeDetector{
return null;
}
}
@ -73,9 +73,9 @@ export class DynamicProtoChangeDetector extends ProtoChangeDetector {
this._pipeRegistry = pipeRegistry;
}
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List) {
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List, directiveMementos:List) {
this._createRecordsIfNecessary(bindingRecords, variableBindings);
return new DynamicChangeDetector(dispatcher, this._pipeRegistry, this._records);
return new DynamicChangeDetector(dispatcher, this._pipeRegistry, this._records, directiveMementos);
}
_createRecordsIfNecessary(bindingRecords:List, variableBindings:List) {
@ -100,12 +100,12 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
this._factory = null;
}
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List) {
this._createFactoryIfNecessary(bindingRecords, variableBindings);
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List, directiveMementos:List) {
this._createFactoryIfNecessary(bindingRecords, variableBindings, directiveMementos);
return this._factory(dispatcher, this._pipeRegistry);
}
_createFactoryIfNecessary(bindingRecords:List, variableBindings:List) {
_createFactoryIfNecessary(bindingRecords:List, variableBindings:List, directiveMementos:List) {
if (isBlank(this._factory)) {
var recordBuilder = new ProtoRecordBuilder();
ListWrapper.forEach(bindingRecords, (r) => {
@ -114,7 +114,7 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
var c = _jitProtoChangeDetectorClassCounter++;
var records = coalesce(recordBuilder.records);
var typeName = `ChangeDetector${c}`;
this._factory = new ChangeDetectorJITGenerator(typeName, records).generate();
this._factory = new ChangeDetectorJITGenerator(typeName, records, directiveMementos).generate();
}
}
}