refactor(change_detect): Flatten Js change detector template

Update the `ChangeDetectionJITGenerator` for clarity and similarity with
the upcoming Dart generated `ChangeDetector` classes.
This commit is contained in:
Tim Blasi 2015-05-27 11:03:20 -07:00
parent 4a3fd5e855
commit a2770c8a52
1 changed files with 244 additions and 367 deletions

View File

@ -1,4 +1,4 @@
import {isPresent, isBlank, BaseException, Type} from 'angular2/src/facade/lang'; import {BaseException, Type, isBlank, isPresent} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {AbstractChangeDetector} from './abstract_change_detector'; import {AbstractChangeDetector} from './abstract_change_detector';
@ -25,10 +25,6 @@ import {
* The code generator takes a list of proto records and creates a function/class * 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 * that "emulates" what the developer would write by hand to implement the same
* kind of behaviour. * kind of behaviour.
*
* 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 ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
var UTIL = "ChangeDetectionUtil"; var UTIL = "ChangeDetectionUtil";
@ -41,240 +37,24 @@ var IS_CHANGED_LOCAL = "isChanged";
var CHANGES_LOCAL = "changes"; var CHANGES_LOCAL = "changes";
var LOCALS_ACCESSOR = "this.locals"; var LOCALS_ACCESSOR = "this.locals";
var MODE_ACCESSOR = "this.mode"; var MODE_ACCESSOR = "this.mode";
var TEMP_LOCAL = "temp";
var CURRENT_PROTO = "currentProto"; var CURRENT_PROTO = "currentProto";
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, directiveRecords);
}
`;
}
function constructorTemplate(type: string, fieldsDefinitions: string): string {
return `
var ${type} = function ${type}(dispatcher, pipeRegistry, protos, directiveRecords) {
${ABSTRACT_CHANGE_DETECTOR}.call(this);
${DISPATCHER_ACCESSOR} = dispatcher;
${PIPE_REGISTRY_ACCESSOR} = pipeRegistry;
${PROTOS_ACCESSOR} = protos;
${DIRECTIVES_ACCESSOR} = directiveRecords;
${LOCALS_ACCESSOR} = null;
${fieldsDefinitions}
}
${type}.prototype = Object.create(${ABSTRACT_CHANGE_DETECTOR}.prototype);
`;
}
function pipeOnDestroyTemplate(pipeNames: List<any>) {
return pipeNames.map((p) => `${p}.onDestroy()`).join("\n");
}
function hydrateTemplate(type: string, mode: string, fieldDefinitions: string,
pipeOnDestroy: string, directiveFieldNames: List<String>,
detectorFieldNames: List<String>): string {
var directiveInit = "";
for (var i = 0; i < directiveFieldNames.length; ++i) {
directiveInit +=
`${directiveFieldNames[i]} = directives.getDirectiveFor(this.directiveRecords[${i}].directiveIndex);\n`;
}
var detectorInit = "";
for (var i = 0; i < detectorFieldNames.length; ++i) {
detectorInit +=
`${detectorFieldNames[i]} = directives.getDetectorFor(this.directiveRecords[${i}].directiveIndex);\n`;
}
return `
${type}.prototype.hydrate = function(context, locals, directives) {
${MODE_ACCESSOR} = "${mode}";
${CONTEXT_ACCESSOR} = context;
${LOCALS_ACCESSOR} = locals;
${directiveInit}
${detectorInit}
}
${type}.prototype.dehydrate = function() {
${pipeOnDestroy}
${fieldDefinitions}
${LOCALS_ACCESSOR} = null;
}
${type}.prototype.hydrated = function() {
return ${CONTEXT_ACCESSOR} !== ${UTIL}.uninitialized();
}
`;
}
function detectChangesTemplate(type: string, body: string): string {
return `
${type}.prototype.detectChangesInRecords = function(throwOnChange) {
${body}
}
`;
}
function callOnAllChangesDoneTemplate(type: string, body: string): string {
return `
${type}.prototype.callOnAllChangesDone = function() {
${body}
}
`;
}
function onAllChangesDoneTemplate(directive: string): string {
return `${directive}.onAllChangesDone();`;
}
function detectChangesBodyTemplate(localDefinitions: string, changeDefinitions: string,
records: string): string {
return `
${localDefinitions}
${changeDefinitions}
var ${TEMP_LOCAL};
var ${IS_CHANGED_LOCAL} = false;
var ${CURRENT_PROTO};
var ${CHANGES_LOCAL} = null;
context = ${CONTEXT_ACCESSOR};
${records}
`;
}
function pipeCheckTemplate(protoIndex: number, context: string, bindingPropagationConfig: string,
pipe: string, pipeType: string, oldValue: string, newValue: string,
change: string, update: string, addToChanges,
lastInDirective: string): string {
return `
${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}];
if (${pipe} === ${UTIL}.uninitialized()) {
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context}, ${bindingPropagationConfig});
} else if (!${pipe}.supports(${context})) {
${pipe}.onDestroy();
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context}, ${bindingPropagationConfig});
}
${newValue} = ${pipe}.transform(${context});
if (${oldValue} !== ${newValue}) {
${newValue} = ${UTIL}.unwrapValue(${newValue});
${change} = true;
${update}
${addToChanges}
${oldValue} = ${newValue};
}
${lastInDirective}
`;
}
function referenceCheckTemplate(protoIndex: number, assignment: string, oldValue: string,
newValue: string, change: string, update: string,
addToChanges: string, lastInDirective: string): string {
return `
${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}];
${assignment}
if (${newValue} !== ${oldValue} || (${newValue} !== ${newValue}) && (${oldValue} !== ${oldValue})) {
${change} = true;
${update}
${addToChanges}
${oldValue} = ${newValue};
}
${lastInDirective}
`;
}
function assignmentTemplate(field: string, value: string) {
return `${field} = ${value};`;
}
function localDefinitionsTemplate(names: List<any>): string {
return names.map((n) => `var ${n};`).join("\n");
}
function changeDefinitionsTemplate(names: List<any>): string {
return names.map((n) => `var ${n} = false;`).join("\n");
}
function fieldDefinitionsTemplate(names: List<any>): string {
return names.map((n) => `${n} = ${UTIL}.uninitialized();`).join("\n");
}
function ifChangedGuardTemplate(changeNames: List<any>, body: string): string {
var cond = changeNames.join(" || ");
return `
if (${cond}) {
${body}
}
`;
}
function addToChangesTemplate(oldValue: string, newValue: string): string {
return `${CHANGES_LOCAL} = ${UTIL}.addChange(${CHANGES_LOCAL}, ${CURRENT_PROTO}.bindingRecord.propertyName, ${UTIL}.simpleChange(${oldValue}, ${newValue}));`;
}
function updateDirectiveTemplate(oldValue: string, newValue: string,
directiveProperty: string): string {
return `
if(throwOnChange) ${UTIL}.throwOnChange(${CURRENT_PROTO}, ${UTIL}.simpleChange(${oldValue}, ${newValue}));
${directiveProperty} = ${newValue};
${IS_CHANGED_LOCAL} = true;
`;
}
function updateElementTemplate(oldValue: string, newValue: string): string {
return `
if(throwOnChange) ${UTIL}.throwOnChange(${CURRENT_PROTO}, ${UTIL}.simpleChange(${oldValue}, ${newValue}));
${DISPATCHER_ACCESSOR}.notifyOnBinding(${CURRENT_PROTO}.bindingRecord, ${newValue});
`;
}
function notifyOnChangesTemplate(directive: string): string {
return `
if(${CHANGES_LOCAL}) {
${directive}.onChange(${CHANGES_LOCAL});
${CHANGES_LOCAL} = null;
}
`;
}
function notifyOnPushDetectorsTemplate(detector: string): string {
return `
if(${IS_CHANGED_LOCAL}) {
${detector}.markAsCheckOnce();
}
`;
}
function lastInDirectiveTemplate(notifyOnChanges: string, notifyOnPush: string): string {
return `
${notifyOnChanges}
${notifyOnPush}
${IS_CHANGED_LOCAL} = false;
`;
}
export class ChangeDetectorJITGenerator { export class ChangeDetectorJITGenerator {
localNames: List<string>; _localNames: List<string>;
changeNames: List<string>; _changeNames: List<string>;
fieldNames: List<string>; _fieldNames: List<string>;
pipeNames: List<string>; _pipeNames: List<string>;
constructor(public typeName: string, public changeDetectionStrategy: string, constructor(public typeName: string, public changeDetectionStrategy: string,
public records: List<ProtoRecord>, public directiveRecords: List<any>) { public records: List<ProtoRecord>, public directiveRecords: List<any>) {
this.localNames = this.getLocalNames(records); this._localNames = this._getLocalNames(records);
this.changeNames = this.getChangeNames(this.localNames); this._changeNames = this._getChangeNames(this._localNames);
this.fieldNames = this.getFieldNames(this.localNames); this._fieldNames = this._getFieldNames(this._localNames);
this.pipeNames = this.getPipeNames(this.localNames); this._pipeNames = this._getPipeNames(this._localNames);
} }
getLocalNames(records: List<ProtoRecord>): List<string> { _getLocalNames(records: List<ProtoRecord>): List<string> {
var index = 0; var index = 0;
var names = records.map((r) => { var names = records.map((r) => {
var sanitizedName = r.name.replace(new RegExp("\\W", "g"), ''); var sanitizedName = r.name.replace(new RegExp("\\W", "g"), '');
@ -283,252 +63,349 @@ export class ChangeDetectorJITGenerator {
return ["context"].concat(names); return ["context"].concat(names);
} }
getChangeNames(localNames: List<string>): List<string> { _getChangeNames(_localNames: List<string>): List<string> {
return localNames.map((n) => `change_${n}`); return _localNames.map((n) => `change_${n}`);
} }
getFieldNames(localNames: List<string>): List<string> { _getFieldNames(_localNames: List<string>): List<string> {
return localNames.map((n) => `this.${n}`); return _localNames.map((n) => `this.${n}`);
} }
getPipeNames(localNames: List<string>): List<string> { _getPipeNames(_localNames: List<string>): List<string> {
return localNames.map((n) => `this.${n}_pipe`); return _localNames.map((n) => `this.${n}_pipe`);
} }
generate(): Function { generate(): Function {
var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(), var classDefinition = `
this.genCallOnAllChangesDone(), this.genHydrate()); var ${this.typeName} = function ${this.typeName}(dispatcher, pipeRegistry, protos, directiveRecords) {
${ABSTRACT_CHANGE_DETECTOR}.call(this);
${DISPATCHER_ACCESSOR} = dispatcher;
${PIPE_REGISTRY_ACCESSOR} = pipeRegistry;
${PROTOS_ACCESSOR} = protos;
${DIRECTIVES_ACCESSOR} = directiveRecords;
${LOCALS_ACCESSOR} = null;
${this._genFieldDefinitions()}
}
${this.typeName}.prototype = Object.create(${ABSTRACT_CHANGE_DETECTOR}.prototype);
${this.typeName}.prototype.detectChangesInRecords = function(throwOnChange) {
${this._genLocalDefinitions()}
${this._genChangeDefinitions()}
var ${IS_CHANGED_LOCAL} = false;
var ${CURRENT_PROTO};
var ${CHANGES_LOCAL} = null;
context = ${CONTEXT_ACCESSOR};
${this.records.map((r) => this._genRecord(r)).join("\n")}
}
${this.typeName}.prototype.callOnAllChangesDone = function() {
${this._genCallOnAllChangesDoneBody()}
}
${this.typeName}.prototype.hydrate = function(context, locals, directives) {
${MODE_ACCESSOR} = "${ChangeDetectionUtil.changeDetectionMode(this.changeDetectionStrategy)}";
${CONTEXT_ACCESSOR} = context;
${LOCALS_ACCESSOR} = locals;
${this._genHydrateDirectives()}
${this._genHydrateDetectors()}
}
${this.typeName}.prototype.dehydrate = function() {
${this._genPipeOnDestroy()}
${this._genFieldDefinitions()}
${LOCALS_ACCESSOR} = null;
}
${this.typeName}.prototype.hydrated = function() {
return ${CONTEXT_ACCESSOR} !== ${UTIL}.uninitialized();
}
return function(dispatcher, pipeRegistry) {
return new ${this.typeName}(dispatcher, pipeRegistry, protos, directiveRecords);
}
`;
return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'protos', return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'protos',
'directiveRecords', text)(AbstractChangeDetector, ChangeDetectionUtil, 'directiveRecords', classDefinition)(
this.records, this.directiveRecords); AbstractChangeDetector, ChangeDetectionUtil, this.records, this.directiveRecords);
} }
genConstructor(): string { _genGetDirectiveFieldNames(): List<string> {
return constructorTemplate(this.typeName, this.genFieldDefinitions()); return this.directiveRecords.map((d) => this._genGetDirective(d.directiveIndex));
} }
genHydrate(): string { _genGetDetectorFieldNames(): List<string> {
var mode = ChangeDetectionUtil.changeDetectionMode(this.changeDetectionStrategy);
return hydrateTemplate(this.typeName, mode, this.genFieldDefinitions(),
pipeOnDestroyTemplate(this.getNonNullPipeNames()),
this.getDirectiveFieldNames(), this.getDetectorFieldNames());
}
getDirectiveFieldNames(): List<string> {
return this.directiveRecords.map((d) => this.getDirective(d.directiveIndex));
}
getDetectorFieldNames(): List<string> {
return this.directiveRecords.filter(r => r.isOnPushChangeDetection()) return this.directiveRecords.filter(r => r.isOnPushChangeDetection())
.map((d) => this.getDetector(d.directiveIndex)); .map((d) => this._genGetDetector(d.directiveIndex));
} }
getDirective(d: DirectiveIndex) { return `this.directive_${d.name}`; } _genGetDirective(d: DirectiveIndex) { return `this.directive_${d.name}`; }
getDetector(d: DirectiveIndex) { return `this.detector_${d.name}`; } _genGetDetector(d: DirectiveIndex) { return `this.detector_${d.name}`; }
genFieldDefinitions() { _getNonNullPipeNames(): List<string> {
var fields = [];
fields = fields.concat(this.fieldNames);
fields = fields.concat(this.getNonNullPipeNames());
fields = fields.concat(this.getDirectiveFieldNames());
fields = fields.concat(this.getDetectorFieldNames());
return fieldDefinitionsTemplate(fields);
}
getNonNullPipeNames(): List<string> {
var pipes = []; var pipes = [];
this.records.forEach((r) => { this.records.forEach((r) => {
if (r.mode === RECORD_TYPE_PIPE || r.mode === RECORD_TYPE_BINDING_PIPE) { if (r.mode === RECORD_TYPE_PIPE || r.mode === RECORD_TYPE_BINDING_PIPE) {
pipes.push(this.pipeNames[r.selfIndex]); pipes.push(this._pipeNames[r.selfIndex]);
} }
}); });
return pipes; return pipes;
} }
genDetectChanges(): string { _genFieldDefinitions() {
var body = this.genDetectChangesBody(); var fields = [];
return detectChangesTemplate(this.typeName, body); fields = fields.concat(this._fieldNames);
fields = fields.concat(this._getNonNullPipeNames());
fields = fields.concat(this._genGetDirectiveFieldNames());
fields = fields.concat(this._genGetDetectorFieldNames());
return fields.map((n) => `${n} = ${UTIL}.uninitialized();`).join("\n");
} }
genCallOnAllChangesDone(): string { _genHydrateDirectives(): string {
var directiveFieldNames = this._genGetDirectiveFieldNames();
var lines = ListWrapper.createFixedSize(directiveFieldNames.length);
for (var i = 0, iLen = directiveFieldNames.length; i < iLen; ++i) {
lines[i] =
`${directiveFieldNames[i]} = directives.getDirectiveFor(${DIRECTIVES_ACCESSOR}[${i}].directiveIndex);`
}
return lines.join('\n');
}
_genHydrateDetectors(): string {
var detectorFieldNames = this._genGetDetectorFieldNames();
var lines = ListWrapper.createFixedSize(detectorFieldNames.length);
for (var i = 0, iLen = detectorFieldNames.length; i < iLen; ++i) {
lines[i] = `${detectorFieldNames[i]} =
directives.getDetectorFor(${DIRECTIVES_ACCESSOR}[${i}].directiveIndex);`
}
return lines.join('\n');
}
_genPipeOnDestroy(): string {
return this._getNonNullPipeNames().map((p) => `${p}.onDestroy();`).join("\n");
}
_genCallOnAllChangesDoneBody(): string {
var notifications = []; var notifications = [];
var dirs = this.directiveRecords; var dirs = this.directiveRecords;
for (var i = dirs.length - 1; i >= 0; --i) { for (var i = dirs.length - 1; i >= 0; --i) {
var dir = dirs[i]; var dir = dirs[i];
if (dir.callOnAllChangesDone) { if (dir.callOnAllChangesDone) {
var directive = `this.directive_${dir.directiveIndex.name}`; notifications.push(`${this._genGetDirective(dir.directiveIndex)}.onAllChangesDone();`);
notifications.push(onAllChangesDoneTemplate(directive));
} }
} }
return callOnAllChangesDoneTemplate(this.typeName, notifications.join(";\n")); return notifications.join("\n");
} }
genDetectChangesBody(): string { _genLocalDefinitions(): string { return this._localNames.map((n) => `var ${n};`).join("\n"); }
var rec = this.records.map((r) => this.genRecord(r)).join("\n");
return detectChangesBodyTemplate(this.genLocalDefinitions(), this.genChangeDefinitions(), rec); _genChangeDefinitions(): string {
return this._changeNames.map((n) => `var ${n} = false;`).join("\n");
} }
genLocalDefinitions(): string { return localDefinitionsTemplate(this.localNames); } _genRecord(r: ProtoRecord): string {
genChangeDefinitions(): string { return changeDefinitionsTemplate(this.changeNames); }
genRecord(r: ProtoRecord): string {
if (r.mode === RECORD_TYPE_PIPE || r.mode === RECORD_TYPE_BINDING_PIPE) { if (r.mode === RECORD_TYPE_PIPE || r.mode === RECORD_TYPE_BINDING_PIPE) {
return this.genPipeCheck(r); return this._genPipeCheck(r);
} else { } else {
return this.genReferenceCheck(r); return this._genReferenceCheck(r);
} }
} }
genPipeCheck(r: ProtoRecord): string { _genPipeCheck(r: ProtoRecord): string {
var context = this.localNames[r.contextIndex]; var context = this._localNames[r.contextIndex];
var oldValue = this.fieldNames[r.selfIndex]; var oldValue = this._fieldNames[r.selfIndex];
var newValue = this.localNames[r.selfIndex]; var newValue = this._localNames[r.selfIndex];
var change = this.changeNames[r.selfIndex]; var change = this._changeNames[r.selfIndex];
var pipe = this.pipeNames[r.selfIndex]; var pipe = this._pipeNames[r.selfIndex];
var cdRef = r.mode === RECORD_TYPE_BINDING_PIPE ? "this.ref" : "null"; var cdRef = r.mode === RECORD_TYPE_BINDING_PIPE ? "this.ref" : "null";
var update = this.genUpdateDirectiveOrElement(r); var protoIndex = r.selfIndex - 1;
var addToChanges = this.genAddToChanges(r); var pipeType = r.name;
var lastInDirective = this.genLastInDirective(r);
return pipeCheckTemplate(r.selfIndex - 1, context, cdRef, pipe, r.name, oldValue, newValue, return `
change, update, addToChanges, lastInDirective); ${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}];
if (${pipe} === ${UTIL}.uninitialized()) {
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context}, ${cdRef});
} else if (!${pipe}.supports(${context})) {
${pipe}.onDestroy();
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context}, ${cdRef});
}
${newValue} = ${pipe}.transform(${context});
if (${oldValue} !== ${newValue}) {
${newValue} = ${UTIL}.unwrapValue(${newValue});
${change} = true;
${this._genUpdateDirectiveOrElement(r)}
${this._genAddToChanges(r)}
${oldValue} = ${newValue};
}
${this._genLastInDirective(r)}
`;
} }
genReferenceCheck(r: ProtoRecord): string { _genReferenceCheck(r: ProtoRecord): string {
var oldValue = this.fieldNames[r.selfIndex]; var oldValue = this._fieldNames[r.selfIndex];
var newValue = this.localNames[r.selfIndex]; var newValue = this._localNames[r.selfIndex];
var change = this.changeNames[r.selfIndex];
var assignment = this.genUpdateCurrentValue(r);
var update = this.genUpdateDirectiveOrElement(r); var protoIndex = r.selfIndex - 1;
var addToChanges = this.genAddToChanges(r); var check = `
var lastInDirective = this.genLastInDirective(r); ${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}];
${this._genUpdateCurrentValue(r)}
if (${newValue} !== ${oldValue}) {
${this._changeNames[r.selfIndex]} = true;
${this._genUpdateDirectiveOrElement(r)}
${this._genAddToChanges(r)}
${oldValue} = ${newValue};
}
${this._genLastInDirective(r)}
`;
var check = referenceCheckTemplate(r.selfIndex - 1, assignment, oldValue, newValue, change,
update, addToChanges, lastInDirective);
if (r.isPureFunction()) { if (r.isPureFunction()) {
return this.ifChangedGuard(r, check); var condition = `${this._changeNames.join(" || ")}`;
return `if (${condition}) { ${check} }`;
} else { } else {
return check; return check;
} }
} }
genUpdateCurrentValue(r: ProtoRecord): string { _genUpdateCurrentValue(r: ProtoRecord): string {
var context = this.getContext(r); var context = (r.contextIndex == -1) ? this._genGetDirective(r.directiveIndex) :
var newValue = this.localNames[r.selfIndex]; this._localNames[r.contextIndex];
var args = this.genArgs(r); var newValue = this._localNames[r.selfIndex];
var argString = r.args.map((arg) => this._localNames[arg]).join(", ");
var rhs;
switch (r.mode) { switch (r.mode) {
case RECORD_TYPE_SELF: case RECORD_TYPE_SELF:
return assignmentTemplate(newValue, context); rhs = context;
break;
case RECORD_TYPE_CONST: case RECORD_TYPE_CONST:
return `${newValue} = ${this.genLiteral(r.funcOrValue)}`; rhs = JSON.stringify(r.funcOrValue);
break;
case RECORD_TYPE_PROPERTY: case RECORD_TYPE_PROPERTY:
return assignmentTemplate(newValue, `${context}.${r.name}`); rhs = `${context}.${r.name}`;
break;
case RECORD_TYPE_LOCAL: case RECORD_TYPE_LOCAL:
return assignmentTemplate(newValue, `${LOCALS_ACCESSOR}.get('${r.name}')`); rhs = `${LOCALS_ACCESSOR}.get('${r.name}')`;
break;
case RECORD_TYPE_INVOKE_METHOD: case RECORD_TYPE_INVOKE_METHOD:
return assignmentTemplate(newValue, `${context}.${r.name}(${args})`); rhs = `${context}.${r.name}(${argString})`;
break;
case RECORD_TYPE_INVOKE_CLOSURE: case RECORD_TYPE_INVOKE_CLOSURE:
return assignmentTemplate(newValue, `${context}(${args})`); rhs = `${context}(${argString})`;
break;
case RECORD_TYPE_PRIMITIVE_OP: case RECORD_TYPE_PRIMITIVE_OP:
return assignmentTemplate(newValue, `${UTIL}.${r.name}(${args})`); rhs = `${UTIL}.${r.name}(${argString})`;
break;
case RECORD_TYPE_INTERPOLATE: case RECORD_TYPE_INTERPOLATE:
return assignmentTemplate(newValue, this.genInterpolation(r)); rhs = this._genInterpolation(r);
break;
case RECORD_TYPE_KEYED_ACCESS: case RECORD_TYPE_KEYED_ACCESS:
var key = this.localNames[r.args[0]]; rhs = `${context}[${this._localNames[r.args[0]]}]`;
return assignmentTemplate(newValue, `${context}[${key}]`); break;
default: default:
throw new BaseException(`Unknown operation ${r.mode}`); throw new BaseException(`Unknown operation ${r.mode}`);
} }
return `${newValue} = ${rhs}`;
} }
getContext(r: ProtoRecord): string { _genInterpolation(r: ProtoRecord): string {
if (r.contextIndex == -1) {
return this.getDirective(r.directiveIndex);
} else {
return this.localNames[r.contextIndex];
}
}
ifChangedGuard(r: ProtoRecord, body: string): string {
return ifChangedGuardTemplate(r.args.map((a) => this.changeNames[a]), body);
}
genInterpolation(r: ProtoRecord): string {
var res = ""; var res = "";
for (var i = 0; i < r.args.length; ++i) { for (var i = 0; i < r.args.length; ++i) {
res += this.genLiteral(r.fixedArgs[i]); res += JSON.stringify(r.fixedArgs[i]);
res += " + "; res += " + ";
res += this.localNames[r.args[i]]; res += this._localNames[r.args[i]];
res += " + "; res += " + ";
} }
res += this.genLiteral(r.fixedArgs[r.args.length]); res += JSON.stringify(r.fixedArgs[r.args.length]);
return res; return res;
} }
genLiteral(value): string { return JSON.stringify(value); } _genUpdateDirectiveOrElement(r: ProtoRecord): string {
genUpdateDirectiveOrElement(r: ProtoRecord): string {
if (!r.lastInBinding) return ""; if (!r.lastInBinding) return "";
var newValue = this.localNames[r.selfIndex]; var newValue = this._localNames[r.selfIndex];
var oldValue = this.fieldNames[r.selfIndex]; var oldValue = this._fieldNames[r.selfIndex];
var br = r.bindingRecord; var br = r.bindingRecord;
if (br.isDirective()) { if (br.isDirective()) {
var directiveProperty = var directiveProperty =
`${this.getDirective(br.directiveRecord.directiveIndex)}.${br.propertyName}`; `${this._genGetDirective(br.directiveRecord.directiveIndex)}.${br.propertyName}`;
return updateDirectiveTemplate(oldValue, newValue, directiveProperty); return `
${this._genThrowOnChangeCheck(oldValue, newValue)}
${directiveProperty} = ${newValue};
${IS_CHANGED_LOCAL} = true;
`;
} else { } else {
return updateElementTemplate(oldValue, newValue); return `
${this._genThrowOnChangeCheck(oldValue, newValue)}
${DISPATCHER_ACCESSOR}.notifyOnBinding(${CURRENT_PROTO}.bindingRecord, ${newValue});
`;
} }
} }
genAddToChanges(r: ProtoRecord): string { _genThrowOnChangeCheck(oldValue: string, newValue: string): string {
var newValue = this.localNames[r.selfIndex]; return `
var oldValue = this.fieldNames[r.selfIndex]; if(throwOnChange) {
return r.bindingRecord.callOnChange() ? addToChangesTemplate(oldValue, newValue) : ""; ${UTIL}.throwOnChange(${CURRENT_PROTO}, ${UTIL}.simpleChange(${oldValue}, ${newValue}));
}
`;
} }
genLastInDirective(r: ProtoRecord): string { _genAddToChanges(r: ProtoRecord): string {
var onChanges = this.genNotifyOnChanges(r); var newValue = this._localNames[r.selfIndex];
var onPush = this.genNotifyOnPushDetectors(r); var oldValue = this._fieldNames[r.selfIndex];
return lastInDirectiveTemplate(onChanges, onPush); if (!r.bindingRecord.callOnChange()) return "";
return `
${CHANGES_LOCAL} = ${UTIL}.addChange(
${CHANGES_LOCAL}, ${CURRENT_PROTO}.bindingRecord.propertyName,
${UTIL}.simpleChange(${oldValue}, ${newValue}));
`;
} }
genNotifyOnChanges(r: ProtoRecord): string { _genLastInDirective(r: ProtoRecord): string {
return `
${this._genNotifyOnChanges(r)}
${this._genNotifyOnPushDetectors(r)}
${IS_CHANGED_LOCAL} = false;
`;
}
_genNotifyOnChanges(r: ProtoRecord): string {
var br = r.bindingRecord; var br = r.bindingRecord;
if (r.lastInDirective && br.callOnChange()) { if (!r.lastInDirective || !br.callOnChange()) return "";
return notifyOnChangesTemplate(this.getDirective(br.directiveRecord.directiveIndex)); return `
} else { if(${CHANGES_LOCAL}) {
return ""; ${this._genGetDirective(br.directiveRecord.directiveIndex)}.onChange(${CHANGES_LOCAL});
} ${CHANGES_LOCAL} = null;
}
`;
} }
genNotifyOnPushDetectors(r: ProtoRecord): string { _genNotifyOnPushDetectors(r: ProtoRecord): string {
var br = r.bindingRecord; var br = r.bindingRecord;
if (r.lastInDirective && br.isOnPushChangeDetection()) { if (!r.lastInDirective || !br.isOnPushChangeDetection()) return "";
return notifyOnPushDetectorsTemplate(this.getDetector(br.directiveRecord.directiveIndex)); var retVal = `
} else { if(${IS_CHANGED_LOCAL}) {
return ""; ${this._genGetDetector(br.directiveRecord.directiveIndex)}.markAsCheckOnce();
} }
`;
return retVal;
} }
genArgs(r: ProtoRecord): string { return r.args.map((arg) => this.localNames[arg]).join(", "); }
} }