chore: move core modules into core directory
BREAKING CHANGE: This change moves the http module into angular2/, so its import path is now angular2/http instead of http/http. Many other modules have also been moved around inside of angular2, but the public API paths have not changed as of this commit.
This commit is contained in:
@ -0,0 +1,287 @@
|
||||
import {isPresent, isBlank, BaseException, StringWrapper} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {ChangeDetectionUtil} from './change_detection_util';
|
||||
import {ChangeDetectorRef} from './change_detector_ref';
|
||||
import {DirectiveIndex} from './directive_record';
|
||||
import {ChangeDetector, ChangeDispatcher} from './interfaces';
|
||||
import {Pipes} from './pipes';
|
||||
import {
|
||||
ChangeDetectionError,
|
||||
ExpressionChangedAfterItHasBeenCheckedException,
|
||||
DehydratedException
|
||||
} from './exceptions';
|
||||
import {BindingTarget} from './binding_record';
|
||||
import {Locals} from './parser/locals';
|
||||
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './constants';
|
||||
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
|
||||
import {isObservable} from './observable_facade';
|
||||
import {ON_PUSH_OBSERVE} from './constants';
|
||||
|
||||
|
||||
var _scope_check: WtfScopeFn = wtfCreateScope(`ChangeDetector#check(ascii id, bool throwOnChange)`);
|
||||
|
||||
class _Context {
|
||||
constructor(public element: any, public componentElement: any, public context: any,
|
||||
public locals: any, public injector: any, public expression: any) {}
|
||||
}
|
||||
|
||||
export class AbstractChangeDetector<T> implements ChangeDetector {
|
||||
lightDomChildren: List<any> = [];
|
||||
shadowDomChildren: List<any> = [];
|
||||
parent: ChangeDetector;
|
||||
ref: ChangeDetectorRef;
|
||||
|
||||
// The names of the below fields must be kept in sync with codegen_name_util.ts or
|
||||
// change detection will fail.
|
||||
alreadyChecked: any = false;
|
||||
context: T;
|
||||
locals: Locals = null;
|
||||
mode: string = null;
|
||||
pipes: Pipes = null;
|
||||
propertyBindingIndex: number;
|
||||
|
||||
// This is an experimental feature. Works only in Dart.
|
||||
subscriptions: any[];
|
||||
streams: any[];
|
||||
|
||||
constructor(public id: string, public dispatcher: ChangeDispatcher,
|
||||
public numberOfPropertyProtoRecords: number, public bindingTargets: BindingTarget[],
|
||||
public directiveIndices: DirectiveIndex[], public strategy: string) {
|
||||
this.ref = new ChangeDetectorRef(this);
|
||||
}
|
||||
|
||||
addChild(cd: ChangeDetector): void {
|
||||
this.lightDomChildren.push(cd);
|
||||
cd.parent = this;
|
||||
}
|
||||
|
||||
removeChild(cd: ChangeDetector): void { ListWrapper.remove(this.lightDomChildren, cd); }
|
||||
|
||||
addShadowDomChild(cd: ChangeDetector): void {
|
||||
this.shadowDomChildren.push(cd);
|
||||
cd.parent = this;
|
||||
}
|
||||
|
||||
removeShadowDomChild(cd: ChangeDetector): void { ListWrapper.remove(this.shadowDomChildren, cd); }
|
||||
|
||||
remove(): void { this.parent.removeChild(this); }
|
||||
|
||||
handleEvent(eventName: string, elIndex: number, locals: Locals): boolean {
|
||||
var res = this.handleEventInternal(eventName, elIndex, locals);
|
||||
this.markPathToRootAsCheckOnce();
|
||||
return res;
|
||||
}
|
||||
|
||||
handleEventInternal(eventName: string, elIndex: number, locals: Locals): boolean { return false; }
|
||||
|
||||
detectChanges(): void { this.runDetectChanges(false); }
|
||||
|
||||
checkNoChanges(): void { throw new BaseException("Not implemented"); }
|
||||
|
||||
runDetectChanges(throwOnChange: boolean): void {
|
||||
if (StringWrapper.equals(this.mode, DETACHED) || StringWrapper.equals(this.mode, CHECKED))
|
||||
return;
|
||||
var s = _scope_check(this.id, throwOnChange);
|
||||
this.detectChangesInRecords(throwOnChange);
|
||||
this._detectChangesInLightDomChildren(throwOnChange);
|
||||
if (throwOnChange === false) this.callOnAllChangesDone();
|
||||
this._detectChangesInShadowDomChildren(throwOnChange);
|
||||
if (StringWrapper.equals(this.mode, CHECK_ONCE)) this.mode = CHECKED;
|
||||
wtfLeave(s);
|
||||
}
|
||||
|
||||
// This method is not intended to be overridden. Subclasses should instead provide an
|
||||
// implementation of `detectChangesInRecordsInternal` which does the work of detecting changes
|
||||
// and which this method will call.
|
||||
// This method expects that `detectChangesInRecordsInternal` will set the property
|
||||
// `this.propertyBindingIndex` to the propertyBindingIndex of the first proto record. This is to
|
||||
// facilitate error reporting.
|
||||
detectChangesInRecords(throwOnChange: boolean): void {
|
||||
if (!this.hydrated()) {
|
||||
this.throwDehydratedError();
|
||||
}
|
||||
try {
|
||||
this.detectChangesInRecordsInternal(throwOnChange);
|
||||
} catch (e) {
|
||||
this._throwError(e, e.stack);
|
||||
}
|
||||
}
|
||||
|
||||
// Subclasses should override this method to perform any work necessary to detect and report
|
||||
// changes. For example, changes should be reported via `ChangeDetectionUtil.addChange`, lifecycle
|
||||
// methods should be called, etc.
|
||||
// This implementation should also set `this.propertyBindingIndex` to the propertyBindingIndex of
|
||||
// the
|
||||
// first proto record to facilitate error reporting. See {@link #detectChangesInRecords}.
|
||||
detectChangesInRecordsInternal(throwOnChange: boolean): void {}
|
||||
|
||||
// This method is not intended to be overridden. Subclasses should instead provide an
|
||||
// implementation of `hydrateDirectives`.
|
||||
hydrate(context: T, locals: Locals, directives: any, pipes: any): void {
|
||||
this.mode = ChangeDetectionUtil.changeDetectionMode(this.strategy);
|
||||
this.context = context;
|
||||
|
||||
if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) {
|
||||
this.observeComponent(context);
|
||||
}
|
||||
|
||||
this.locals = locals;
|
||||
this.pipes = pipes;
|
||||
this.hydrateDirectives(directives);
|
||||
this.alreadyChecked = false;
|
||||
}
|
||||
|
||||
// Subclasses should override this method to hydrate any directives.
|
||||
hydrateDirectives(directives: any): void {}
|
||||
|
||||
// This method is not intended to be overridden. Subclasses should instead provide an
|
||||
// implementation of `dehydrateDirectives`.
|
||||
dehydrate(): void {
|
||||
this.dehydrateDirectives(true);
|
||||
|
||||
// This is an experimental feature. Works only in Dart.
|
||||
if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) {
|
||||
this._unsubsribeFromObservables();
|
||||
}
|
||||
|
||||
this.context = null;
|
||||
this.locals = null;
|
||||
this.pipes = null;
|
||||
}
|
||||
|
||||
// Subclasses should override this method to dehydrate any directives. This method should reverse
|
||||
// any work done in `hydrateDirectives`.
|
||||
dehydrateDirectives(destroyPipes: boolean): void {}
|
||||
|
||||
hydrated(): boolean { return this.context !== null; }
|
||||
|
||||
callOnAllChangesDone(): void { this.dispatcher.notifyOnAllChangesDone(); }
|
||||
|
||||
_detectChangesInLightDomChildren(throwOnChange: boolean): void {
|
||||
var c = this.lightDomChildren;
|
||||
for (var i = 0; i < c.length; ++i) {
|
||||
c[i].runDetectChanges(throwOnChange);
|
||||
}
|
||||
}
|
||||
|
||||
_detectChangesInShadowDomChildren(throwOnChange: boolean): void {
|
||||
var c = this.shadowDomChildren;
|
||||
for (var i = 0; i < c.length; ++i) {
|
||||
c[i].runDetectChanges(throwOnChange);
|
||||
}
|
||||
}
|
||||
|
||||
markAsCheckOnce(): void { this.mode = CHECK_ONCE; }
|
||||
|
||||
markPathToRootAsCheckOnce(): void {
|
||||
var c: ChangeDetector = this;
|
||||
while (isPresent(c) && !StringWrapper.equals(c.mode, DETACHED)) {
|
||||
if (StringWrapper.equals(c.mode, CHECKED)) c.mode = CHECK_ONCE;
|
||||
c = c.parent;
|
||||
}
|
||||
}
|
||||
|
||||
// This is an experimental feature. Works only in Dart.
|
||||
private _unsubsribeFromObservables(): void {
|
||||
if (isPresent(this.subscriptions)) {
|
||||
for (var i = 0; i < this.subscriptions.length; ++i) {
|
||||
var s = this.subscriptions[i];
|
||||
if (isPresent(this.subscriptions[i])) {
|
||||
s.cancel();
|
||||
this.subscriptions[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is an experimental feature. Works only in Dart.
|
||||
protected observeValue(value: any, index: number): any {
|
||||
if (isObservable(value)) {
|
||||
this._createArrayToStoreObservables();
|
||||
if (isBlank(this.subscriptions[index])) {
|
||||
this.streams[index] = value.changes;
|
||||
this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck());
|
||||
} else if (this.streams[index] !== value.changes) {
|
||||
this.subscriptions[index].cancel();
|
||||
this.streams[index] = value.changes;
|
||||
this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck());
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// This is an experimental feature. Works only in Dart.
|
||||
protected observeDirective(value: any, index: number): any {
|
||||
if (isObservable(value)) {
|
||||
this._createArrayToStoreObservables();
|
||||
var arrayIndex = this.numberOfPropertyProtoRecords + index + 2; // +1 is component
|
||||
this.streams[arrayIndex] = value.changes;
|
||||
this.subscriptions[arrayIndex] = value.changes.listen((_) => this.ref.requestCheck());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// This is an experimental feature. Works only in Dart.
|
||||
protected observeComponent(value: any): any {
|
||||
if (isObservable(value)) {
|
||||
this._createArrayToStoreObservables();
|
||||
var index = this.numberOfPropertyProtoRecords + 1;
|
||||
this.streams[index] = value.changes;
|
||||
this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private _createArrayToStoreObservables(): void {
|
||||
if (isBlank(this.subscriptions)) {
|
||||
this.subscriptions = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords +
|
||||
this.directiveIndices.length + 2);
|
||||
this.streams = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords +
|
||||
this.directiveIndices.length + 2);
|
||||
}
|
||||
}
|
||||
|
||||
protected getDirectiveFor(directives: any, index: number): any {
|
||||
return directives.getDirectiveFor(this.directiveIndices[index]);
|
||||
}
|
||||
|
||||
protected getDetectorFor(directives: any, index: number): ChangeDetector {
|
||||
return directives.getDetectorFor(this.directiveIndices[index]);
|
||||
}
|
||||
|
||||
protected notifyDispatcher(value: any): void {
|
||||
this.dispatcher.notifyOnBinding(this._currentBinding(), value);
|
||||
}
|
||||
|
||||
protected logBindingUpdate(value: any): void {
|
||||
this.dispatcher.logBindingUpdate(this._currentBinding(), value);
|
||||
}
|
||||
|
||||
protected addChange(changes: StringMap<string, any>, oldValue: any,
|
||||
newValue: any): StringMap<string, any> {
|
||||
if (isBlank(changes)) {
|
||||
changes = {};
|
||||
}
|
||||
changes[this._currentBinding().name] = ChangeDetectionUtil.simpleChange(oldValue, newValue);
|
||||
return changes;
|
||||
}
|
||||
|
||||
private _throwError(exception: any, stack: any): void {
|
||||
var c = this.dispatcher.getDebugContext(this._currentBinding().elementIndex, null);
|
||||
var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.context, c.locals,
|
||||
c.injector, this._currentBinding().debug) :
|
||||
null;
|
||||
throw new ChangeDetectionError(this._currentBinding().debug, exception, stack, context);
|
||||
}
|
||||
|
||||
protected throwOnChangeError(oldValue: any, newValue: any): void {
|
||||
throw new ExpressionChangedAfterItHasBeenCheckedException(this._currentBinding().debug,
|
||||
oldValue, newValue, null);
|
||||
}
|
||||
|
||||
protected throwDehydratedError(): void { throw new DehydratedException(); }
|
||||
|
||||
private _currentBinding(): BindingTarget {
|
||||
return this.bindingTargets[this.propertyBindingIndex];
|
||||
}
|
||||
}
|
148
modules/angular2/src/core/change_detection/binding_record.ts
Normal file
148
modules/angular2/src/core/change_detection/binding_record.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||
import {SetterFn} from 'angular2/src/reflection/types';
|
||||
import {AST} from './parser/ast';
|
||||
import {DirectiveIndex, DirectiveRecord} from './directive_record';
|
||||
|
||||
const DIRECTIVE_LIFECYCLE = "directiveLifecycle";
|
||||
const BINDING = "native";
|
||||
|
||||
const DIRECTIVE = "directive";
|
||||
const ELEMENT_PROPERTY = "elementProperty";
|
||||
const ELEMENT_ATTRIBUTE = "elementAttribute";
|
||||
const ELEMENT_CLASS = "elementClass";
|
||||
const ELEMENT_STYLE = "elementStyle";
|
||||
const TEXT_NODE = "textNode";
|
||||
const EVENT = "event";
|
||||
const HOST_EVENT = "hostEvent";
|
||||
|
||||
export class BindingTarget {
|
||||
constructor(public mode: string, public elementIndex: number, public name: string,
|
||||
public unit: string, public debug: string) {}
|
||||
|
||||
isDirective(): boolean { return this.mode === DIRECTIVE; }
|
||||
|
||||
isElementProperty(): boolean { return this.mode === ELEMENT_PROPERTY; }
|
||||
|
||||
isElementAttribute(): boolean { return this.mode === ELEMENT_ATTRIBUTE; }
|
||||
|
||||
isElementClass(): boolean { return this.mode === ELEMENT_CLASS; }
|
||||
|
||||
isElementStyle(): boolean { return this.mode === ELEMENT_STYLE; }
|
||||
|
||||
isTextNode(): boolean { return this.mode === TEXT_NODE; }
|
||||
}
|
||||
|
||||
export class BindingRecord {
|
||||
constructor(public mode: string, public target: BindingTarget, public implicitReceiver: any,
|
||||
public ast: AST, public setter: SetterFn, public lifecycleEvent: string,
|
||||
public directiveRecord: DirectiveRecord) {}
|
||||
|
||||
isDirectiveLifecycle(): boolean { return this.mode === DIRECTIVE_LIFECYCLE; }
|
||||
|
||||
callOnChange(): boolean {
|
||||
return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange;
|
||||
}
|
||||
|
||||
isDefaultChangeDetection(): boolean {
|
||||
return isBlank(this.directiveRecord) || this.directiveRecord.isDefaultChangeDetection();
|
||||
}
|
||||
|
||||
|
||||
static createDirectiveOnCheck(directiveRecord: DirectiveRecord): BindingRecord {
|
||||
return new BindingRecord(DIRECTIVE_LIFECYCLE, null, 0, null, null, "onCheck", directiveRecord);
|
||||
}
|
||||
|
||||
static createDirectiveOnInit(directiveRecord: DirectiveRecord): BindingRecord {
|
||||
return new BindingRecord(DIRECTIVE_LIFECYCLE, null, 0, null, null, "onInit", directiveRecord);
|
||||
}
|
||||
|
||||
static createDirectiveOnChange(directiveRecord: DirectiveRecord): BindingRecord {
|
||||
return new BindingRecord(DIRECTIVE_LIFECYCLE, null, 0, null, null, "onChange", directiveRecord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static createForDirective(ast: AST, propertyName: string, setter: SetterFn,
|
||||
directiveRecord: DirectiveRecord): BindingRecord {
|
||||
var elementIndex = directiveRecord.directiveIndex.elementIndex;
|
||||
var t = new BindingTarget(DIRECTIVE, elementIndex, propertyName, null, ast.toString());
|
||||
return new BindingRecord(DIRECTIVE, t, 0, ast, setter, null, directiveRecord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static createForElementProperty(ast: AST, elementIndex: number,
|
||||
propertyName: string): BindingRecord {
|
||||
var t = new BindingTarget(ELEMENT_PROPERTY, elementIndex, propertyName, null, ast.toString());
|
||||
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
|
||||
}
|
||||
|
||||
static createForElementAttribute(ast: AST, elementIndex: number,
|
||||
attributeName: string): BindingRecord {
|
||||
var t = new BindingTarget(ELEMENT_ATTRIBUTE, elementIndex, attributeName, null, ast.toString());
|
||||
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
|
||||
}
|
||||
|
||||
static createForElementClass(ast: AST, elementIndex: number, className: string): BindingRecord {
|
||||
var t = new BindingTarget(ELEMENT_CLASS, elementIndex, className, null, ast.toString());
|
||||
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
|
||||
}
|
||||
|
||||
static createForElementStyle(ast: AST, elementIndex: number, styleName: string,
|
||||
unit: string): BindingRecord {
|
||||
var t = new BindingTarget(ELEMENT_STYLE, elementIndex, styleName, unit, ast.toString());
|
||||
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static createForHostProperty(directiveIndex: DirectiveIndex, ast: AST,
|
||||
propertyName: string): BindingRecord {
|
||||
var t = new BindingTarget(ELEMENT_PROPERTY, directiveIndex.elementIndex, propertyName, null,
|
||||
ast.toString());
|
||||
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
|
||||
}
|
||||
|
||||
static createForHostAttribute(directiveIndex: DirectiveIndex, ast: AST,
|
||||
attributeName: string): BindingRecord {
|
||||
var t = new BindingTarget(ELEMENT_ATTRIBUTE, directiveIndex.elementIndex, attributeName, null,
|
||||
ast.toString());
|
||||
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
|
||||
}
|
||||
|
||||
static createForHostClass(directiveIndex: DirectiveIndex, ast: AST,
|
||||
className: string): BindingRecord {
|
||||
var t = new BindingTarget(ELEMENT_CLASS, directiveIndex.elementIndex, className, null,
|
||||
ast.toString());
|
||||
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
|
||||
}
|
||||
|
||||
static createForHostStyle(directiveIndex: DirectiveIndex, ast: AST, styleName: string,
|
||||
unit: string): BindingRecord {
|
||||
var t = new BindingTarget(ELEMENT_STYLE, directiveIndex.elementIndex, styleName, unit,
|
||||
ast.toString());
|
||||
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static createForTextNode(ast: AST, elementIndex: number): BindingRecord {
|
||||
var t = new BindingTarget(TEXT_NODE, elementIndex, null, null, ast.toString());
|
||||
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static createForEvent(ast: AST, eventName: string, elementIndex: number): BindingRecord {
|
||||
var t = new BindingTarget(EVENT, elementIndex, eventName, null, ast.toString());
|
||||
return new BindingRecord(EVENT, t, 0, ast, null, null, null);
|
||||
}
|
||||
|
||||
static createForHostEvent(ast: AST, eventName: string,
|
||||
directiveRecord: DirectiveRecord): BindingRecord {
|
||||
var directiveIndex = directiveRecord.directiveIndex;
|
||||
var t =
|
||||
new BindingTarget(HOST_EVENT, directiveIndex.elementIndex, eventName, null, ast.toString());
|
||||
return new BindingRecord(HOST_EVENT, t, directiveIndex, ast, null, null, directiveRecord);
|
||||
}
|
||||
}
|
171
modules/angular2/src/core/change_detection/change_detection.ts
Normal file
171
modules/angular2/src/core/change_detection/change_detection.ts
Normal file
@ -0,0 +1,171 @@
|
||||
import {JitProtoChangeDetector} from './jit_proto_change_detector';
|
||||
import {PregenProtoChangeDetector} from './pregen_proto_change_detector';
|
||||
import {DynamicProtoChangeDetector} from './proto_change_detector';
|
||||
import {IterableDiffers, IterableDifferFactory} from './differs/iterable_differs';
|
||||
import {DefaultIterableDifferFactory} from './differs/default_iterable_differ';
|
||||
import {KeyValueDiffers, KeyValueDifferFactory} from './differs/keyvalue_differs';
|
||||
import {DefaultKeyValueDifferFactory} from './differs/default_keyvalue_differ';
|
||||
import {
|
||||
ChangeDetection,
|
||||
ProtoChangeDetector,
|
||||
ChangeDetectorDefinition,
|
||||
ChangeDetectorGenConfig
|
||||
} from './interfaces';
|
||||
import {Injector, Inject, Injectable, OpaqueToken, Optional, Binding} from 'angular2/di';
|
||||
import {List, StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {
|
||||
CONST,
|
||||
CONST_EXPR,
|
||||
isPresent,
|
||||
BaseException,
|
||||
assertionsEnabled
|
||||
} from 'angular2/src/facade/lang';
|
||||
|
||||
export {
|
||||
ASTWithSource,
|
||||
AST,
|
||||
AstTransformer,
|
||||
PropertyRead,
|
||||
LiteralArray,
|
||||
ImplicitReceiver
|
||||
} from './parser/ast';
|
||||
|
||||
export {Lexer} from './parser/lexer';
|
||||
export {Parser} from './parser/parser';
|
||||
export {Locals} from './parser/locals';
|
||||
|
||||
export {
|
||||
DehydratedException,
|
||||
ExpressionChangedAfterItHasBeenCheckedException,
|
||||
ChangeDetectionError
|
||||
} from './exceptions';
|
||||
export {
|
||||
ProtoChangeDetector,
|
||||
ChangeDetector,
|
||||
ChangeDispatcher,
|
||||
ChangeDetection,
|
||||
ChangeDetectorDefinition,
|
||||
DebugContext,
|
||||
ChangeDetectorGenConfig
|
||||
} from './interfaces';
|
||||
export {CHECK_ONCE, CHECK_ALWAYS, DETACHED, CHECKED, ON_PUSH, DEFAULT} from './constants';
|
||||
export {DynamicProtoChangeDetector} from './proto_change_detector';
|
||||
export {BindingRecord, BindingTarget} from './binding_record';
|
||||
export {DirectiveIndex, DirectiveRecord} from './directive_record';
|
||||
export {DynamicChangeDetector} from './dynamic_change_detector';
|
||||
export {ChangeDetectorRef} from './change_detector_ref';
|
||||
export {IterableDiffers, IterableDiffer, IterableDifferFactory} from './differs/iterable_differs';
|
||||
export {KeyValueDiffers, KeyValueDiffer, KeyValueDifferFactory} from './differs/keyvalue_differs';
|
||||
export {PipeTransform, PipeOnDestroy} from './pipe_transform';
|
||||
export {WrappedValue} from './change_detection_util';
|
||||
|
||||
/**
|
||||
* Structural diffing for `Object`s and `Map`s.
|
||||
*/
|
||||
export const keyValDiff: KeyValueDifferFactory[] =
|
||||
CONST_EXPR([CONST_EXPR(new DefaultKeyValueDifferFactory())]);
|
||||
|
||||
/**
|
||||
* Structural diffing for `Iterable` types such as `Array`s.
|
||||
*/
|
||||
export const iterableDiff: IterableDifferFactory[] =
|
||||
CONST_EXPR([CONST_EXPR(new DefaultIterableDifferFactory())]);
|
||||
|
||||
export const defaultIterableDiffers = CONST_EXPR(new IterableDiffers(iterableDiff));
|
||||
|
||||
export const defaultKeyValueDiffers = CONST_EXPR(new KeyValueDiffers(keyValDiff));
|
||||
|
||||
/**
|
||||
* Map from {@link ChangeDetectorDefinition#id} to a factory method which takes a
|
||||
* {@link Pipes} and a {@link ChangeDetectorDefinition} and generates a
|
||||
* {@link ProtoChangeDetector} associated with the definition.
|
||||
*/
|
||||
// TODO(kegluneq): Use PregenProtoChangeDetectorFactory rather than Function once possible in
|
||||
// dart2js. See https://github.com/dart-lang/sdk/issues/23630 for details.
|
||||
export var preGeneratedProtoDetectors: StringMap<string, Function> = {};
|
||||
|
||||
/**
|
||||
* Implements change detection using a map of pregenerated proto detectors.
|
||||
*/
|
||||
@Injectable()
|
||||
export class PreGeneratedChangeDetection extends ChangeDetection {
|
||||
_dynamicChangeDetection: ChangeDetection;
|
||||
_protoChangeDetectorFactories: StringMap<string, Function>;
|
||||
_genConfig: ChangeDetectorGenConfig;
|
||||
|
||||
constructor(config?: ChangeDetectorGenConfig,
|
||||
protoChangeDetectorsForTest?: StringMap<string, Function>) {
|
||||
super();
|
||||
this._dynamicChangeDetection = new DynamicChangeDetection();
|
||||
this._protoChangeDetectorFactories = isPresent(protoChangeDetectorsForTest) ?
|
||||
protoChangeDetectorsForTest :
|
||||
preGeneratedProtoDetectors;
|
||||
|
||||
this._genConfig =
|
||||
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
|
||||
assertionsEnabled(), false);
|
||||
}
|
||||
|
||||
static isSupported(): boolean { return PregenProtoChangeDetector.isSupported(); }
|
||||
|
||||
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
|
||||
if (StringMapWrapper.contains(this._protoChangeDetectorFactories, id)) {
|
||||
return StringMapWrapper.get(this._protoChangeDetectorFactories, id)(definition);
|
||||
}
|
||||
return this._dynamicChangeDetection.getProtoChangeDetector(id, definition);
|
||||
}
|
||||
|
||||
get genConfig(): ChangeDetectorGenConfig { return this._genConfig; }
|
||||
get generateDetectors(): boolean { return true; }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implements change detection that does not require `eval()`.
|
||||
*
|
||||
* This is slower than {@link JitChangeDetection}.
|
||||
*/
|
||||
@Injectable()
|
||||
export class DynamicChangeDetection extends ChangeDetection {
|
||||
_genConfig: ChangeDetectorGenConfig;
|
||||
|
||||
constructor(config?: ChangeDetectorGenConfig) {
|
||||
super();
|
||||
this._genConfig =
|
||||
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
|
||||
assertionsEnabled(), false);
|
||||
}
|
||||
|
||||
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
|
||||
return new DynamicProtoChangeDetector(definition);
|
||||
}
|
||||
|
||||
get genConfig(): ChangeDetectorGenConfig { return this._genConfig; }
|
||||
get generateDetectors(): boolean { return true; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements faster change detection by generating source code.
|
||||
*
|
||||
* This requires `eval()`. For change detection that does not require `eval()`, see
|
||||
* {@link DynamicChangeDetection} and {@link PreGeneratedChangeDetection}.
|
||||
*/
|
||||
@Injectable()
|
||||
export class JitChangeDetection extends ChangeDetection {
|
||||
_genConfig: ChangeDetectorGenConfig;
|
||||
constructor(config?: ChangeDetectorGenConfig) {
|
||||
super();
|
||||
this._genConfig =
|
||||
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
|
||||
assertionsEnabled(), false);
|
||||
}
|
||||
|
||||
static isSupported(): boolean { return JitProtoChangeDetector.isSupported(); }
|
||||
|
||||
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
|
||||
return new JitProtoChangeDetector(definition);
|
||||
}
|
||||
|
||||
get genConfig(): ChangeDetectorGenConfig { return this._genConfig; }
|
||||
get generateDetectors(): boolean { return true; }
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
library change_detection.change_detection_jit_generator;
|
||||
|
||||
/// Placeholder JIT generator for Dart.
|
||||
/// Dart does not support `eval`, so JIT generation is not an option. Instead,
|
||||
/// the Dart transformer pre-generates these Change Detector classes and
|
||||
/// registers them with the system. See `PreGeneratedChangeDetection`,
|
||||
/// `PregenProtoChangeDetector`, and
|
||||
/// `src/transform/template_compiler/change_detector_codegen.dart` for details.
|
||||
class ChangeDetectorJITGenerator {
|
||||
ChangeDetectorJITGenerator(typeName, strategy, records, directiveMementos) {}
|
||||
|
||||
generate() {
|
||||
throw "Jit Change Detection is not supported in Dart";
|
||||
}
|
||||
|
||||
static bool isSupported() => false;
|
||||
}
|
@ -0,0 +1,384 @@
|
||||
import {BaseException, Type, isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {AbstractChangeDetector} from './abstract_change_detector';
|
||||
import {ChangeDetectionUtil} from './change_detection_util';
|
||||
import {DirectiveIndex, DirectiveRecord} from './directive_record';
|
||||
|
||||
import {ProtoRecord, RecordType} from './proto_record';
|
||||
import {CodegenNameUtil, sanitizeName} from './codegen_name_util';
|
||||
import {CodegenLogicUtil} from './codegen_logic_util';
|
||||
import {codify} from './codegen_facade';
|
||||
import {EventBinding} from './event_binding';
|
||||
import {BindingTarget} from './binding_record';
|
||||
import {ChangeDetectorGenConfig} from './interfaces';
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* This code should be kept in sync with the Dart transformer's
|
||||
* `angular2.transform.template_compiler.change_detector_codegen` library. If you make updates
|
||||
* here, please make equivalent changes there.
|
||||
*/
|
||||
var ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
|
||||
var UTIL = "ChangeDetectionUtil";
|
||||
var IS_CHANGED_LOCAL = "isChanged";
|
||||
var CHANGES_LOCAL = "changes";
|
||||
|
||||
export class ChangeDetectorJITGenerator {
|
||||
_logic: CodegenLogicUtil;
|
||||
_names: CodegenNameUtil;
|
||||
_typeName: string;
|
||||
|
||||
constructor(private id: string, private changeDetectionStrategy: string,
|
||||
private records: List<ProtoRecord>, private propertyBindingTargets: BindingTarget[],
|
||||
private eventBindings: EventBinding[], private directiveRecords: List<any>,
|
||||
private genConfig: ChangeDetectorGenConfig) {
|
||||
this._names =
|
||||
new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords, UTIL);
|
||||
this._logic = new CodegenLogicUtil(this._names, UTIL, changeDetectionStrategy);
|
||||
this._typeName = sanitizeName(`ChangeDetector_${this.id}`);
|
||||
}
|
||||
|
||||
generate(): Function {
|
||||
var classDefinition = `
|
||||
var ${this._typeName} = function ${this._typeName}(dispatcher) {
|
||||
${ABSTRACT_CHANGE_DETECTOR}.call(
|
||||
this, ${JSON.stringify(this.id)}, dispatcher, ${this.records.length},
|
||||
${this._typeName}.gen_propertyBindingTargets, ${this._typeName}.gen_directiveIndices,
|
||||
${codify(this.changeDetectionStrategy)});
|
||||
this.dehydrateDirectives(false);
|
||||
}
|
||||
|
||||
${this._typeName}.prototype = Object.create(${ABSTRACT_CHANGE_DETECTOR}.prototype);
|
||||
|
||||
${this._typeName}.prototype.detectChangesInRecordsInternal = function(throwOnChange) {
|
||||
${this._names.genInitLocals()}
|
||||
var ${IS_CHANGED_LOCAL} = false;
|
||||
var ${CHANGES_LOCAL} = null;
|
||||
|
||||
${this.records.map((r) => this._genRecord(r)).join("\n")}
|
||||
|
||||
${this._names.getAlreadyCheckedName()} = true;
|
||||
}
|
||||
|
||||
${this._maybeGenHandleEventInternal()}
|
||||
|
||||
${this._genCheckNoChanges()}
|
||||
|
||||
${this._maybeGenCallOnAllChangesDone()}
|
||||
|
||||
${this._maybeGenHydrateDirectives()}
|
||||
|
||||
${this._maybeGenDehydrateDirectives()}
|
||||
|
||||
${this._genPropertyBindingTargets()};
|
||||
|
||||
${this._genDirectiveIndices()};
|
||||
|
||||
return function(dispatcher) {
|
||||
return new ${this._typeName}(dispatcher);
|
||||
}
|
||||
`;
|
||||
return new Function(ABSTRACT_CHANGE_DETECTOR, UTIL, classDefinition)(AbstractChangeDetector,
|
||||
ChangeDetectionUtil);
|
||||
}
|
||||
|
||||
_genPropertyBindingTargets(): string {
|
||||
var targets = this._logic.genPropertyBindingTargets(this.propertyBindingTargets,
|
||||
this.genConfig.genDebugInfo);
|
||||
return `${this._typeName}.gen_propertyBindingTargets = ${targets};`;
|
||||
}
|
||||
|
||||
_genDirectiveIndices(): string {
|
||||
var indices = this._logic.genDirectiveIndices(this.directiveRecords);
|
||||
return `${this._typeName}.gen_directiveIndices = ${indices};`;
|
||||
}
|
||||
|
||||
_maybeGenHandleEventInternal(): string {
|
||||
if (this.eventBindings.length > 0) {
|
||||
var handlers = this.eventBindings.map(eb => this._genEventBinding(eb)).join("\n");
|
||||
return `
|
||||
${this._typeName}.prototype.handleEventInternal = function(eventName, elIndex, locals) {
|
||||
var ${this._names.getPreventDefaultAccesor()} = false;
|
||||
${this._names.genInitEventLocals()}
|
||||
${handlers}
|
||||
return ${this._names.getPreventDefaultAccesor()};
|
||||
}
|
||||
`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
_genEventBinding(eb: EventBinding): string {
|
||||
var recs = eb.records.map(r => this._genEventBindingEval(eb, r)).join("\n");
|
||||
return `
|
||||
if (eventName === "${eb.eventName}" && elIndex === ${eb.elIndex}) {
|
||||
${recs}
|
||||
}`;
|
||||
}
|
||||
|
||||
_genEventBindingEval(eb: EventBinding, r: ProtoRecord): string {
|
||||
if (r.lastInBinding) {
|
||||
var evalRecord = this._logic.genEventBindingEvalValue(eb, r);
|
||||
var markPath = this._genMarkPathToRootAsCheckOnce(r);
|
||||
var prevDefault = this._genUpdatePreventDefault(eb, r);
|
||||
return `${evalRecord}\n${markPath}\n${prevDefault}`;
|
||||
} else {
|
||||
return this._logic.genEventBindingEvalValue(eb, r);
|
||||
}
|
||||
}
|
||||
|
||||
_genMarkPathToRootAsCheckOnce(r: ProtoRecord): string {
|
||||
var br = r.bindingRecord;
|
||||
if (br.isDefaultChangeDetection()) {
|
||||
return "";
|
||||
} else {
|
||||
return `${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markPathToRootAsCheckOnce();`;
|
||||
}
|
||||
}
|
||||
|
||||
_genUpdatePreventDefault(eb: EventBinding, r: ProtoRecord): string {
|
||||
var local = this._names.getEventLocalName(eb, r.selfIndex);
|
||||
return `if (${local} === false) { ${this._names.getPreventDefaultAccesor()} = true};`;
|
||||
}
|
||||
|
||||
_maybeGenDehydrateDirectives(): string {
|
||||
var destroyPipesCode = this._names.genPipeOnDestroy();
|
||||
if (destroyPipesCode) {
|
||||
destroyPipesCode = `if (destroyPipes) { ${destroyPipesCode} }`;
|
||||
}
|
||||
var dehydrateFieldsCode = this._names.genDehydrateFields();
|
||||
if (!destroyPipesCode && !dehydrateFieldsCode) return '';
|
||||
return `${this._typeName}.prototype.dehydrateDirectives = function(destroyPipes) {
|
||||
${destroyPipesCode}
|
||||
${dehydrateFieldsCode}
|
||||
}`;
|
||||
}
|
||||
|
||||
_maybeGenHydrateDirectives(): string {
|
||||
var hydrateDirectivesCode = this._logic.genHydrateDirectives(this.directiveRecords);
|
||||
var hydrateDetectorsCode = this._logic.genHydrateDetectors(this.directiveRecords);
|
||||
if (!hydrateDirectivesCode && !hydrateDetectorsCode) return '';
|
||||
return `${this._typeName}.prototype.hydrateDirectives = function(directives) {
|
||||
${hydrateDirectivesCode}
|
||||
${hydrateDetectorsCode}
|
||||
}`;
|
||||
}
|
||||
|
||||
_maybeGenCallOnAllChangesDone(): string {
|
||||
var notifications = [];
|
||||
var dirs = this.directiveRecords;
|
||||
|
||||
// NOTE(kegluneq): Order is important!
|
||||
for (var i = dirs.length - 1; i >= 0; --i) {
|
||||
var dir = dirs[i];
|
||||
if (dir.callOnAllChangesDone) {
|
||||
notifications.push(
|
||||
`${this._names.getDirectiveName(dir.directiveIndex)}.onAllChangesDone();`);
|
||||
}
|
||||
}
|
||||
if (notifications.length > 0) {
|
||||
var directiveNotifications = notifications.join("\n");
|
||||
return `
|
||||
${this._typeName}.prototype.callOnAllChangesDone = function() {
|
||||
${ABSTRACT_CHANGE_DETECTOR}.prototype.callOnAllChangesDone.call(this);
|
||||
${directiveNotifications}
|
||||
}
|
||||
`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
_genRecord(r: ProtoRecord): string {
|
||||
var rec;
|
||||
if (r.isLifeCycleRecord()) {
|
||||
rec = this._genDirectiveLifecycle(r);
|
||||
} else if (r.isPipeRecord()) {
|
||||
rec = this._genPipeCheck(r);
|
||||
} else {
|
||||
rec = this._genReferenceCheck(r);
|
||||
}
|
||||
return `
|
||||
${this._maybeFirstInBinding(r)}
|
||||
${rec}
|
||||
${this._maybeGenLastInDirective(r)}
|
||||
`;
|
||||
}
|
||||
|
||||
_genDirectiveLifecycle(r: ProtoRecord): string {
|
||||
if (r.name === "onCheck") {
|
||||
return this._genOnCheck(r);
|
||||
} else if (r.name === "onInit") {
|
||||
return this._genOnInit(r);
|
||||
} else if (r.name === "onChange") {
|
||||
return this._genOnChange(r);
|
||||
} else {
|
||||
throw new BaseException(`Unknown lifecycle event '${r.name}'`);
|
||||
}
|
||||
}
|
||||
|
||||
_genPipeCheck(r: ProtoRecord): string {
|
||||
var context = this._names.getLocalName(r.contextIndex);
|
||||
var argString = r.args.map((arg) => this._names.getLocalName(arg)).join(", ");
|
||||
|
||||
var oldValue = this._names.getFieldName(r.selfIndex);
|
||||
var newValue = this._names.getLocalName(r.selfIndex);
|
||||
|
||||
var pipe = this._names.getPipeName(r.selfIndex);
|
||||
var pipeType = r.name;
|
||||
var read = `
|
||||
if (${pipe} === ${UTIL}.uninitialized) {
|
||||
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}');
|
||||
}
|
||||
${newValue} = ${pipe}.transform(${context}, [${argString}]);
|
||||
`;
|
||||
|
||||
var check = `
|
||||
if (${oldValue} !== ${newValue}) {
|
||||
${newValue} = ${UTIL}.unwrapValue(${newValue})
|
||||
${this._genChangeMarker(r)}
|
||||
${this._genUpdateDirectiveOrElement(r)}
|
||||
${this._genAddToChanges(r)}
|
||||
${oldValue} = ${newValue};
|
||||
}
|
||||
`;
|
||||
|
||||
return r.shouldBeChecked() ? `${read}${check}` : read;
|
||||
}
|
||||
|
||||
_genReferenceCheck(r: ProtoRecord): string {
|
||||
var oldValue = this._names.getFieldName(r.selfIndex);
|
||||
var newValue = this._names.getLocalName(r.selfIndex);
|
||||
var read = `
|
||||
${this._logic.genPropertyBindingEvalValue(r)}
|
||||
`;
|
||||
|
||||
var check = `
|
||||
if (${newValue} !== ${oldValue}) {
|
||||
${this._genChangeMarker(r)}
|
||||
${this._genUpdateDirectiveOrElement(r)}
|
||||
${this._genAddToChanges(r)}
|
||||
${oldValue} = ${newValue};
|
||||
}
|
||||
`;
|
||||
|
||||
var genCode = r.shouldBeChecked() ? `${read}${check}` : read;
|
||||
|
||||
if (r.isPureFunction()) {
|
||||
var condition = r.args.map((a) => this._names.getChangeName(a)).join(" || ");
|
||||
if (r.isUsedByOtherRecord()) {
|
||||
return `if (${condition}) { ${genCode} } else { ${newValue} = ${oldValue}; }`;
|
||||
} else {
|
||||
return `if (${condition}) { ${genCode} }`;
|
||||
}
|
||||
} else {
|
||||
return genCode;
|
||||
}
|
||||
}
|
||||
|
||||
_genChangeMarker(r: ProtoRecord): string {
|
||||
return r.argumentToPureFunction ? `${this._names.getChangeName(r.selfIndex)} = true` : ``;
|
||||
}
|
||||
|
||||
_genUpdateDirectiveOrElement(r: ProtoRecord): string {
|
||||
if (!r.lastInBinding) return "";
|
||||
|
||||
var newValue = this._names.getLocalName(r.selfIndex);
|
||||
var oldValue = this._names.getFieldName(r.selfIndex);
|
||||
var notifyDebug = this.genConfig.logBindingUpdate ? `this.logBindingUpdate(${newValue});` : "";
|
||||
|
||||
var br = r.bindingRecord;
|
||||
if (br.target.isDirective()) {
|
||||
var directiveProperty =
|
||||
`${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.${br.target.name}`;
|
||||
return `
|
||||
${this._genThrowOnChangeCheck(oldValue, newValue)}
|
||||
${directiveProperty} = ${newValue};
|
||||
${notifyDebug}
|
||||
${IS_CHANGED_LOCAL} = true;
|
||||
`;
|
||||
} else {
|
||||
return `
|
||||
${this._genThrowOnChangeCheck(oldValue, newValue)}
|
||||
this.notifyDispatcher(${newValue});
|
||||
${notifyDebug}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
_genThrowOnChangeCheck(oldValue: string, newValue: string): string {
|
||||
if (this.genConfig.genCheckNoChanges) {
|
||||
return `
|
||||
if(throwOnChange) {
|
||||
this.throwOnChangeError(${oldValue}, ${newValue});
|
||||
}
|
||||
`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
_genCheckNoChanges(): string {
|
||||
if (this.genConfig.genCheckNoChanges) {
|
||||
return `${this._typeName}.prototype.checkNoChanges = function() { this.runDetectChanges(true); }`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
_genAddToChanges(r: ProtoRecord): string {
|
||||
var newValue = this._names.getLocalName(r.selfIndex);
|
||||
var oldValue = this._names.getFieldName(r.selfIndex);
|
||||
if (!r.bindingRecord.callOnChange()) return "";
|
||||
return `${CHANGES_LOCAL} = this.addChange(${CHANGES_LOCAL}, ${oldValue}, ${newValue});`;
|
||||
}
|
||||
|
||||
_maybeFirstInBinding(r: ProtoRecord): string {
|
||||
var prev = ChangeDetectionUtil.protoByIndex(this.records, r.selfIndex - 1);
|
||||
var firstInBindng = isBlank(prev) || prev.bindingRecord !== r.bindingRecord;
|
||||
return firstInBindng && !r.bindingRecord.isDirectiveLifecycle() ?
|
||||
`${this._names.getPropertyBindingIndex()} = ${r.propertyBindingIndex};` :
|
||||
'';
|
||||
}
|
||||
|
||||
_maybeGenLastInDirective(r: ProtoRecord): string {
|
||||
if (!r.lastInDirective) return "";
|
||||
return `
|
||||
${CHANGES_LOCAL} = null;
|
||||
${this._genNotifyOnPushDetectors(r)}
|
||||
${IS_CHANGED_LOCAL} = false;
|
||||
`;
|
||||
}
|
||||
|
||||
_genOnCheck(r: ProtoRecord): string {
|
||||
var br = r.bindingRecord;
|
||||
return `if (!throwOnChange) ${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.onCheck();`;
|
||||
}
|
||||
|
||||
_genOnInit(r: ProtoRecord): string {
|
||||
var br = r.bindingRecord;
|
||||
return `if (!throwOnChange && !${this._names.getAlreadyCheckedName()}) ${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.onInit();`;
|
||||
}
|
||||
|
||||
_genOnChange(r: ProtoRecord): string {
|
||||
var br = r.bindingRecord;
|
||||
return `if (!throwOnChange && ${CHANGES_LOCAL}) ${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.onChange(${CHANGES_LOCAL});`;
|
||||
}
|
||||
|
||||
_genNotifyOnPushDetectors(r: ProtoRecord): string {
|
||||
var br = r.bindingRecord;
|
||||
if (!r.lastInDirective || br.isDefaultChangeDetection()) return "";
|
||||
var retVal = `
|
||||
if(${IS_CHANGED_LOCAL}) {
|
||||
${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markAsCheckOnce();
|
||||
}
|
||||
`;
|
||||
return retVal;
|
||||
}
|
||||
}
|
@ -0,0 +1,215 @@
|
||||
import {
|
||||
CONST_EXPR,
|
||||
isPresent,
|
||||
isBlank,
|
||||
BaseException,
|
||||
Type,
|
||||
StringWrapper
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {ProtoRecord} from './proto_record';
|
||||
import {
|
||||
CHECK_ALWAYS,
|
||||
CHECK_ONCE,
|
||||
CHECKED,
|
||||
DETACHED,
|
||||
isDefaultChangeDetectionStrategy
|
||||
} from './constants';
|
||||
import {implementsOnDestroy} from './pipe_lifecycle_reflector';
|
||||
import {BindingTarget} from './binding_record';
|
||||
import {DirectiveIndex} from './directive_record';
|
||||
|
||||
|
||||
/**
|
||||
* Indicates that the result of a {@link PipeMetadata} transformation has changed even though the
|
||||
* reference
|
||||
* has not changed.
|
||||
*
|
||||
* The wrapped value will be unwrapped by change detection, and the unwrapped value will be stored.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* if (this._latestValue === this._latestReturnedValue) {
|
||||
* return this._latestReturnedValue;
|
||||
* } else {
|
||||
* this._latestReturnedValue = this._latestValue;
|
||||
* return WrappedValue.wrap(this._latestValue); // this will force update
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export class WrappedValue {
|
||||
constructor(public wrapped: any) {}
|
||||
|
||||
static wrap(value: any): WrappedValue {
|
||||
var w = _wrappedValues[_wrappedIndex++ % 5];
|
||||
w.wrapped = value;
|
||||
return w;
|
||||
}
|
||||
}
|
||||
|
||||
var _wrappedValues = [
|
||||
new WrappedValue(null),
|
||||
new WrappedValue(null),
|
||||
new WrappedValue(null),
|
||||
new WrappedValue(null),
|
||||
new WrappedValue(null)
|
||||
];
|
||||
|
||||
var _wrappedIndex = 0;
|
||||
|
||||
|
||||
export class SimpleChange {
|
||||
constructor(public previousValue: any, public currentValue: any) {}
|
||||
|
||||
isFirstChange(): boolean { return this.previousValue === ChangeDetectionUtil.uninitialized; }
|
||||
}
|
||||
|
||||
var _simpleChangesIndex = 0;
|
||||
var _simpleChanges = [
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null)
|
||||
];
|
||||
|
||||
function _simpleChange(previousValue, currentValue): SimpleChange {
|
||||
var index = _simpleChangesIndex++ % 20;
|
||||
var s = _simpleChanges[index];
|
||||
s.previousValue = previousValue;
|
||||
s.currentValue = currentValue;
|
||||
return s;
|
||||
}
|
||||
|
||||
/* tslint:disable:requireParameterType */
|
||||
export class ChangeDetectionUtil {
|
||||
static uninitialized: Object = CONST_EXPR<Object>(new Object());
|
||||
|
||||
static arrayFn0(): any[] { return []; }
|
||||
static arrayFn1(a1): any[] { return [a1]; }
|
||||
static arrayFn2(a1, a2): any[] { return [a1, a2]; }
|
||||
static arrayFn3(a1, a2, a3): any[] { return [a1, a2, a3]; }
|
||||
static arrayFn4(a1, a2, a3, a4): any[] { return [a1, a2, a3, a4]; }
|
||||
static arrayFn5(a1, a2, a3, a4, a5): any[] { return [a1, a2, a3, a4, a5]; }
|
||||
static arrayFn6(a1, a2, a3, a4, a5, a6): any[] { return [a1, a2, a3, a4, a5, a6]; }
|
||||
static arrayFn7(a1, a2, a3, a4, a5, a6, a7): any[] { return [a1, a2, a3, a4, a5, a6, a7]; }
|
||||
static arrayFn8(a1, a2, a3, a4, a5, a6, a7, a8): any[] {
|
||||
return [a1, a2, a3, a4, a5, a6, a7, a8];
|
||||
}
|
||||
static arrayFn9(a1, a2, a3, a4, a5, a6, a7, a8, a9): any[] {
|
||||
return [a1, a2, a3, a4, a5, a6, a7, a8, a9];
|
||||
}
|
||||
|
||||
static operation_negate(value): any { return !value; }
|
||||
static operation_add(left, right): any { return left + right; }
|
||||
static operation_subtract(left, right): any { return left - right; }
|
||||
static operation_multiply(left, right): any { return left * right; }
|
||||
static operation_divide(left, right): any { return left / right; }
|
||||
static operation_remainder(left, right): any { return left % right; }
|
||||
static operation_equals(left, right): any { return left == right; }
|
||||
static operation_not_equals(left, right): any { return left != right; }
|
||||
static operation_identical(left, right): any { return left === right; }
|
||||
static operation_not_identical(left, right): any { return left !== right; }
|
||||
static operation_less_then(left, right): any { return left < right; }
|
||||
static operation_greater_then(left, right): any { return left > right; }
|
||||
static operation_less_or_equals_then(left, right): any { return left <= right; }
|
||||
static operation_greater_or_equals_then(left, right): any { return left >= right; }
|
||||
static operation_logical_and(left, right): any { return left && right; }
|
||||
static operation_logical_or(left, right): any { return left || right; }
|
||||
static cond(cond, trueVal, falseVal): any { return cond ? trueVal : falseVal; }
|
||||
|
||||
static mapFn(keys: List<any>): any {
|
||||
function buildMap(values): StringMap<any, any> {
|
||||
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): any { return obj[args[0]]; }
|
||||
|
||||
static unwrapValue(value: any): any {
|
||||
if (value instanceof WrappedValue) {
|
||||
return value.wrapped;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
static changeDetectionMode(strategy: string): string {
|
||||
return isDefaultChangeDetectionStrategy(strategy) ? CHECK_ALWAYS : CHECK_ONCE;
|
||||
}
|
||||
|
||||
static simpleChange(previousValue: any, currentValue: any): SimpleChange {
|
||||
return _simpleChange(previousValue, currentValue);
|
||||
}
|
||||
|
||||
static isValueBlank(value: any): boolean { return isBlank(value); }
|
||||
|
||||
static s(value: any): string { return isPresent(value) ? `${value}` : ''; }
|
||||
|
||||
static protoByIndex(protos: ProtoRecord[], selfIndex: number): ProtoRecord {
|
||||
return selfIndex < 1 ?
|
||||
null :
|
||||
protos[selfIndex - 1]; // self index is shifted by one because of context
|
||||
}
|
||||
|
||||
static callPipeOnDestroy(pipe: any): void {
|
||||
if (implementsOnDestroy(pipe)) {
|
||||
pipe.onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
static bindingTarget(mode: string, elementIndex: number, name: string, unit: string,
|
||||
debug: string): BindingTarget {
|
||||
return new BindingTarget(mode, elementIndex, name, unit, debug);
|
||||
}
|
||||
|
||||
static directiveIndex(elementIndex: number, directiveIndex: number): DirectiveIndex {
|
||||
return new DirectiveIndex(elementIndex, directiveIndex);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import {ChangeDetector} from './interfaces';
|
||||
import {CHECK_ONCE, DETACHED, CHECK_ALWAYS} from './constants';
|
||||
|
||||
/**
|
||||
* Controls change detection.
|
||||
*
|
||||
* {@link ChangeDetectorRef} allows requesting checks for detectors that rely on observables. It
|
||||
* also allows detaching and attaching change detector subtrees.
|
||||
*/
|
||||
export class ChangeDetectorRef {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
constructor(private _cd: ChangeDetector) {}
|
||||
|
||||
/**
|
||||
* Request to check all ON_PUSH ancestors.
|
||||
*/
|
||||
requestCheck(): void { this._cd.markPathToRootAsCheckOnce(); }
|
||||
|
||||
/**
|
||||
* Detaches the change detector from the change detector tree.
|
||||
*
|
||||
* The detached change detector will not be checked until it is reattached.
|
||||
*/
|
||||
detach(): void { this._cd.mode = DETACHED; }
|
||||
|
||||
/**
|
||||
* Reattach the change detector to the change detector tree.
|
||||
*
|
||||
* This also requests a check of this change detector. This reattached change detector will be
|
||||
*checked during the
|
||||
* next change detection run.
|
||||
*/
|
||||
reattach(): void {
|
||||
this._cd.mode = CHECK_ALWAYS;
|
||||
this.requestCheck();
|
||||
}
|
||||
}
|
81
modules/angular2/src/core/change_detection/coalesce.ts
Normal file
81
modules/angular2/src/core/change_detection/coalesce.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import {isPresent, isBlank, looseIdentical} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper, Map} from 'angular2/src/facade/collection';
|
||||
import {RecordType, ProtoRecord} from './proto_record';
|
||||
|
||||
/**
|
||||
* Removes "duplicate" records. It assuming that record evaluation does not
|
||||
* have side-effects.
|
||||
*
|
||||
* Records that are not last in bindings are removed and all the indices
|
||||
* of the records that depend on them are updated.
|
||||
*
|
||||
* Records that are last in bindings CANNOT be removed, and instead are
|
||||
* replaced with very cheap SELF records.
|
||||
*/
|
||||
export function coalesce(records: ProtoRecord[]): ProtoRecord[] {
|
||||
var res: List<ProtoRecord> = [];
|
||||
var indexMap: Map<number, number> = new Map<number, number>();
|
||||
|
||||
for (var i = 0; i < records.length; ++i) {
|
||||
var r = records[i];
|
||||
var record = _replaceIndices(r, res.length + 1, indexMap);
|
||||
var matchingRecord = _findMatching(record, res);
|
||||
|
||||
if (isPresent(matchingRecord) && record.lastInBinding) {
|
||||
res.push(_selfRecord(record, matchingRecord.selfIndex, res.length + 1));
|
||||
indexMap.set(r.selfIndex, matchingRecord.selfIndex);
|
||||
matchingRecord.referencedBySelf = true;
|
||||
|
||||
} else if (isPresent(matchingRecord) && !record.lastInBinding) {
|
||||
if (record.argumentToPureFunction) {
|
||||
matchingRecord.argumentToPureFunction = true;
|
||||
}
|
||||
|
||||
indexMap.set(r.selfIndex, matchingRecord.selfIndex);
|
||||
|
||||
} else {
|
||||
res.push(record);
|
||||
indexMap.set(r.selfIndex, record.selfIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function _selfRecord(r: ProtoRecord, contextIndex: number, selfIndex: number): ProtoRecord {
|
||||
return new ProtoRecord(RecordType.SELF, "self", null, [], r.fixedArgs, contextIndex,
|
||||
r.directiveIndex, selfIndex, r.bindingRecord, r.lastInBinding,
|
||||
r.lastInDirective, false, false, r.propertyBindingIndex);
|
||||
}
|
||||
|
||||
function _findMatching(r: ProtoRecord, rs: List<ProtoRecord>) {
|
||||
return ListWrapper.find(
|
||||
rs, (rr) => rr.mode !== RecordType.DIRECTIVE_LIFECYCLE && _sameDirIndex(rr, r) &&
|
||||
rr.mode === r.mode && looseIdentical(rr.funcOrValue, r.funcOrValue) &&
|
||||
rr.contextIndex === r.contextIndex && looseIdentical(rr.name, r.name) &&
|
||||
ListWrapper.equals(rr.args, r.args));
|
||||
}
|
||||
|
||||
function _sameDirIndex(a: ProtoRecord, b: ProtoRecord): boolean {
|
||||
var di1 = isBlank(a.directiveIndex) ? null : a.directiveIndex.directiveIndex;
|
||||
var ei1 = isBlank(a.directiveIndex) ? null : a.directiveIndex.elementIndex;
|
||||
|
||||
var di2 = isBlank(b.directiveIndex) ? null : b.directiveIndex.directiveIndex;
|
||||
var ei2 = isBlank(b.directiveIndex) ? null : b.directiveIndex.elementIndex;
|
||||
|
||||
return di1 === di2 && ei1 === ei2;
|
||||
}
|
||||
|
||||
function _replaceIndices(r: ProtoRecord, selfIndex: number, indexMap: Map<any, any>) {
|
||||
var args = ListWrapper.map(r.args, (a) => _map(indexMap, a));
|
||||
var contextIndex = _map(indexMap, r.contextIndex);
|
||||
return new ProtoRecord(r.mode, r.name, r.funcOrValue, args, r.fixedArgs, contextIndex,
|
||||
r.directiveIndex, selfIndex, r.bindingRecord, r.lastInBinding,
|
||||
r.lastInDirective, r.argumentToPureFunction, r.referencedBySelf,
|
||||
r.propertyBindingIndex);
|
||||
}
|
||||
|
||||
function _map(indexMap: Map<any, any>, value: number) {
|
||||
var r = indexMap.get(value);
|
||||
return isPresent(r) ? r : value;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
library angular2.src.change_detection.codegen_facade;
|
||||
|
||||
import 'dart:convert' show JSON;
|
||||
|
||||
/// Converts `funcOrValue` to a string which can be used in generated code.
|
||||
String codify(funcOrValue) => JSON.encode(funcOrValue).replaceAll(r'$', r'\$');
|
||||
|
||||
/// Combine the strings of generated code into a single interpolated string.
|
||||
/// Each element of `vals` is expected to be a string literal or a codegen'd
|
||||
/// call to a method returning a string.
|
||||
/// The return format interpolates each value as an expression which reads
|
||||
/// poorly, but the resulting code is easily flattened by dart2js.
|
||||
String combineGeneratedStrings(List<String> vals) {
|
||||
return '"${vals.map((v) => '\${$v}').join('')}"';
|
||||
}
|
||||
|
||||
String rawString(String str) {
|
||||
return "r'$str'";
|
||||
}
|
21
modules/angular2/src/core/change_detection/codegen_facade.ts
Normal file
21
modules/angular2/src/core/change_detection/codegen_facade.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import {List} from 'angular2/src/facade/collection';
|
||||
|
||||
/**
|
||||
* Converts `funcOrValue` to a string which can be used in generated code.
|
||||
*/
|
||||
export function codify(obj: any): string {
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
|
||||
export function rawString(str: string): string {
|
||||
return `'${str}'`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the strings of generated code into a single interpolated string.
|
||||
* Each element of `vals` is expected to be a string literal or a codegen'd
|
||||
* call to a method returning a string.
|
||||
*/
|
||||
export function combineGeneratedStrings(vals: string[]): string {
|
||||
return vals.join(' + ');
|
||||
}
|
180
modules/angular2/src/core/change_detection/codegen_logic_util.ts
Normal file
180
modules/angular2/src/core/change_detection/codegen_logic_util.ts
Normal file
@ -0,0 +1,180 @@
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {BaseException, Json, StringWrapper, isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||
import {CodegenNameUtil} from './codegen_name_util';
|
||||
import {codify, combineGeneratedStrings, rawString} from './codegen_facade';
|
||||
import {ProtoRecord, RecordType} from './proto_record';
|
||||
import {BindingTarget} from './binding_record';
|
||||
import {DirectiveRecord} from './directive_record';
|
||||
import {ON_PUSH_OBSERVE} from './constants';
|
||||
|
||||
/**
|
||||
* Class responsible for providing change detection logic for chagne detector classes.
|
||||
*/
|
||||
export class CodegenLogicUtil {
|
||||
constructor(private _names: CodegenNameUtil, private _utilName: string,
|
||||
private _changeDetection: string) {}
|
||||
|
||||
/**
|
||||
* Generates a statement which updates the local variable representing `protoRec` with the current
|
||||
* value of the record. Used by property bindings.
|
||||
*/
|
||||
genPropertyBindingEvalValue(protoRec: ProtoRecord): string {
|
||||
return this.genEvalValue(protoRec, idx => this._names.getLocalName(idx),
|
||||
this._names.getLocalsAccessorName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a statement which updates the local variable representing `protoRec` with the current
|
||||
* value of the record. Used by event bindings.
|
||||
*/
|
||||
genEventBindingEvalValue(eventRecord: any, protoRec: ProtoRecord): string {
|
||||
return this.genEvalValue(protoRec, idx => this._names.getEventLocalName(eventRecord, idx),
|
||||
"locals");
|
||||
}
|
||||
|
||||
private genEvalValue(protoRec: ProtoRecord, getLocalName: Function,
|
||||
localsAccessor: string): string {
|
||||
var context = (protoRec.contextIndex == -1) ?
|
||||
this._names.getDirectiveName(protoRec.directiveIndex) :
|
||||
getLocalName(protoRec.contextIndex);
|
||||
var argString = ListWrapper.map(protoRec.args, (arg) => getLocalName(arg)).join(", ");
|
||||
|
||||
var rhs: string;
|
||||
switch (protoRec.mode) {
|
||||
case RecordType.SELF:
|
||||
rhs = context;
|
||||
break;
|
||||
|
||||
case RecordType.CONST:
|
||||
rhs = codify(protoRec.funcOrValue);
|
||||
break;
|
||||
|
||||
case RecordType.PROPERTY_READ:
|
||||
rhs = this._observe(`${context}.${protoRec.name}`, protoRec);
|
||||
break;
|
||||
|
||||
case RecordType.SAFE_PROPERTY:
|
||||
var read = this._observe(`${context}.${protoRec.name}`, protoRec);
|
||||
rhs =
|
||||
`${this._utilName}.isValueBlank(${context}) ? null : ${this._observe(read, protoRec)}`;
|
||||
break;
|
||||
|
||||
case RecordType.PROPERTY_WRITE:
|
||||
rhs = `${context}.${protoRec.name} = ${getLocalName(protoRec.args[0])}`;
|
||||
break;
|
||||
|
||||
case RecordType.LOCAL:
|
||||
rhs = this._observe(`${localsAccessor}.get(${rawString(protoRec.name)})`, protoRec);
|
||||
break;
|
||||
|
||||
case RecordType.INVOKE_METHOD:
|
||||
rhs = this._observe(`${context}.${protoRec.name}(${argString})`, protoRec);
|
||||
break;
|
||||
|
||||
case RecordType.SAFE_INVOKE_METHOD:
|
||||
var invoke = `${context}.${protoRec.name}(${argString})`;
|
||||
rhs =
|
||||
`${this._utilName}.isValueBlank(${context}) ? null : ${this._observe(invoke, protoRec)}`;
|
||||
break;
|
||||
|
||||
case RecordType.INVOKE_CLOSURE:
|
||||
rhs = `${context}(${argString})`;
|
||||
break;
|
||||
|
||||
case RecordType.PRIMITIVE_OP:
|
||||
rhs = `${this._utilName}.${protoRec.name}(${argString})`;
|
||||
break;
|
||||
|
||||
case RecordType.COLLECTION_LITERAL:
|
||||
rhs = `${this._utilName}.${protoRec.name}(${argString})`;
|
||||
break;
|
||||
|
||||
case RecordType.INTERPOLATE:
|
||||
rhs = this._genInterpolation(protoRec);
|
||||
break;
|
||||
|
||||
case RecordType.KEYED_READ:
|
||||
rhs = this._observe(`${context}[${getLocalName(protoRec.args[0])}]`, protoRec);
|
||||
break;
|
||||
|
||||
case RecordType.KEYED_WRITE:
|
||||
rhs = `${context}[${getLocalName(protoRec.args[0])}] = ${getLocalName(protoRec.args[1])}`;
|
||||
break;
|
||||
|
||||
case RecordType.CHAIN:
|
||||
rhs = 'null';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new BaseException(`Unknown operation ${protoRec.mode}`);
|
||||
}
|
||||
return `${getLocalName(protoRec.selfIndex)} = ${rhs};`;
|
||||
}
|
||||
|
||||
_observe(exp: string, rec: ProtoRecord): string {
|
||||
// This is an experimental feature. Works only in Dart.
|
||||
if (StringWrapper.equals(this._changeDetection, ON_PUSH_OBSERVE)) {
|
||||
return `this.observeValue(${exp}, ${rec.selfIndex})`;
|
||||
} else {
|
||||
return exp;
|
||||
}
|
||||
}
|
||||
|
||||
genPropertyBindingTargets(propertyBindingTargets: BindingTarget[],
|
||||
genDebugInfo: boolean): string {
|
||||
var bs = propertyBindingTargets.map(b => {
|
||||
if (isBlank(b)) return "null";
|
||||
|
||||
var debug = genDebugInfo ? codify(b.debug) : "null";
|
||||
return `${this._utilName}.bindingTarget(${codify(b.mode)}, ${b.elementIndex}, ${codify(b.name)}, ${codify(b.unit)}, ${debug})`;
|
||||
});
|
||||
return `[${bs.join(", ")}]`;
|
||||
}
|
||||
|
||||
genDirectiveIndices(directiveRecords: DirectiveRecord[]): string {
|
||||
var bs = directiveRecords.map(
|
||||
b =>
|
||||
`${this._utilName}.directiveIndex(${b.directiveIndex.elementIndex}, ${b.directiveIndex.directiveIndex})`);
|
||||
return `[${bs.join(", ")}]`;
|
||||
}
|
||||
|
||||
_genInterpolation(protoRec: ProtoRecord): string {
|
||||
var iVals = [];
|
||||
for (var i = 0; i < protoRec.args.length; ++i) {
|
||||
iVals.push(codify(protoRec.fixedArgs[i]));
|
||||
iVals.push(`${this._utilName}.s(${this._names.getLocalName(protoRec.args[i])})`);
|
||||
}
|
||||
iVals.push(codify(protoRec.fixedArgs[protoRec.args.length]));
|
||||
return combineGeneratedStrings(iVals);
|
||||
}
|
||||
|
||||
genHydrateDirectives(directiveRecords: DirectiveRecord[]): string {
|
||||
var res = [];
|
||||
for (var i = 0; i < directiveRecords.length; ++i) {
|
||||
var r = directiveRecords[i];
|
||||
res.push(`${this._names.getDirectiveName(r.directiveIndex)} = ${this._genReadDirective(i)};`);
|
||||
}
|
||||
return res.join("\n");
|
||||
}
|
||||
|
||||
private _genReadDirective(index: number) {
|
||||
// This is an experimental feature. Works only in Dart.
|
||||
if (StringWrapper.equals(this._changeDetection, ON_PUSH_OBSERVE)) {
|
||||
return `this.observeDirective(this.getDirectiveFor(directives, ${index}), ${index})`;
|
||||
} else {
|
||||
return `this.getDirectiveFor(directives, ${index})`;
|
||||
}
|
||||
}
|
||||
|
||||
genHydrateDetectors(directiveRecords: DirectiveRecord[]): string {
|
||||
var res = [];
|
||||
for (var i = 0; i < directiveRecords.length; ++i) {
|
||||
var r = directiveRecords[i];
|
||||
if (!r.isDefaultChangeDetection()) {
|
||||
res.push(
|
||||
`${this._names.getDetectorName(r.directiveIndex)} = this.getDetectorFor(directives, ${i});`);
|
||||
}
|
||||
}
|
||||
return res.join("\n");
|
||||
}
|
||||
}
|
198
modules/angular2/src/core/change_detection/codegen_name_util.ts
Normal file
198
modules/angular2/src/core/change_detection/codegen_name_util.ts
Normal file
@ -0,0 +1,198 @@
|
||||
import {RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper, MapWrapper, Map} from 'angular2/src/facade/collection';
|
||||
|
||||
import {DirectiveIndex} from './directive_record';
|
||||
|
||||
import {ProtoRecord} from './proto_record';
|
||||
import {EventBinding} from './event_binding';
|
||||
|
||||
// The names of these fields must be kept in sync with abstract_change_detector.ts or change
|
||||
// detection will fail.
|
||||
const _ALREADY_CHECKED_ACCESSOR = "alreadyChecked";
|
||||
const _CONTEXT_ACCESSOR = "context";
|
||||
const _PROP_BINDING_INDEX = "propertyBindingIndex";
|
||||
const _DIRECTIVES_ACCESSOR = "directiveIndices";
|
||||
const _DISPATCHER_ACCESSOR = "dispatcher";
|
||||
const _LOCALS_ACCESSOR = "locals";
|
||||
const _MODE_ACCESSOR = "mode";
|
||||
const _PIPES_ACCESSOR = "pipes";
|
||||
const _PROTOS_ACCESSOR = "protos";
|
||||
|
||||
// `context` is always first.
|
||||
export const CONTEXT_INDEX = 0;
|
||||
const _FIELD_PREFIX = 'this.';
|
||||
|
||||
var _whiteSpaceRegExp = RegExpWrapper.create("\\W", "g");
|
||||
|
||||
/**
|
||||
* Returns `s` with all non-identifier characters removed.
|
||||
*/
|
||||
export function sanitizeName(s: string): string {
|
||||
return StringWrapper.replaceAll(s, _whiteSpaceRegExp, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Class responsible for providing field and local variable names for change detector classes.
|
||||
* Also provides some convenience functions, for example, declaring variables, destroying pipes,
|
||||
* and dehydrating the detector.
|
||||
*/
|
||||
export class CodegenNameUtil {
|
||||
/**
|
||||
* Record names sanitized for use as fields.
|
||||
* See [sanitizeName] for details.
|
||||
*/
|
||||
_sanitizedNames: List<string>;
|
||||
_sanitizedEventNames: Map<EventBinding, List<string>>;
|
||||
|
||||
constructor(private records: List<ProtoRecord>, private eventBindings: EventBinding[],
|
||||
private directiveRecords: List<any>, private utilName: string) {
|
||||
this._sanitizedNames = ListWrapper.createFixedSize(this.records.length + 1);
|
||||
this._sanitizedNames[CONTEXT_INDEX] = _CONTEXT_ACCESSOR;
|
||||
for (var i = 0, iLen = this.records.length; i < iLen; ++i) {
|
||||
this._sanitizedNames[i + 1] = sanitizeName(`${this.records[i].name}${i}`);
|
||||
}
|
||||
|
||||
this._sanitizedEventNames = new Map();
|
||||
for (var ebIndex = 0; ebIndex < eventBindings.length; ++ebIndex) {
|
||||
var eb = eventBindings[ebIndex];
|
||||
var names = [_CONTEXT_ACCESSOR];
|
||||
for (var i = 0, iLen = eb.records.length; i < iLen; ++i) {
|
||||
names.push(sanitizeName(`${eb.records[i].name}${i}_${ebIndex}`));
|
||||
}
|
||||
this._sanitizedEventNames.set(eb, names);
|
||||
}
|
||||
}
|
||||
|
||||
_addFieldPrefix(name: string): string { return `${_FIELD_PREFIX}${name}`; }
|
||||
|
||||
getDispatcherName(): string { return this._addFieldPrefix(_DISPATCHER_ACCESSOR); }
|
||||
|
||||
getPipesAccessorName(): string { return this._addFieldPrefix(_PIPES_ACCESSOR); }
|
||||
|
||||
getProtosName(): string { return this._addFieldPrefix(_PROTOS_ACCESSOR); }
|
||||
|
||||
getDirectivesAccessorName(): string { return this._addFieldPrefix(_DIRECTIVES_ACCESSOR); }
|
||||
|
||||
getLocalsAccessorName(): string { return this._addFieldPrefix(_LOCALS_ACCESSOR); }
|
||||
|
||||
getAlreadyCheckedName(): string { return this._addFieldPrefix(_ALREADY_CHECKED_ACCESSOR); }
|
||||
|
||||
getModeName(): string { return this._addFieldPrefix(_MODE_ACCESSOR); }
|
||||
|
||||
getPropertyBindingIndex(): string { return this._addFieldPrefix(_PROP_BINDING_INDEX); }
|
||||
|
||||
getLocalName(idx: number): string { return `l_${this._sanitizedNames[idx]}`; }
|
||||
|
||||
getEventLocalName(eb: EventBinding, idx: number): string {
|
||||
return `l_${MapWrapper.get(this._sanitizedEventNames, eb)[idx]}`;
|
||||
}
|
||||
|
||||
getChangeName(idx: number): string { return `c_${this._sanitizedNames[idx]}`; }
|
||||
|
||||
/**
|
||||
* Generate a statement initializing local variables used when detecting changes.
|
||||
*/
|
||||
genInitLocals(): string {
|
||||
var declarations = [];
|
||||
var assignments = [];
|
||||
for (var i = 0, iLen = this.getFieldCount(); i < iLen; ++i) {
|
||||
if (i == CONTEXT_INDEX) {
|
||||
declarations.push(`${this.getLocalName(i)} = ${this.getFieldName(i)}`);
|
||||
} else {
|
||||
var rec = this.records[i - 1];
|
||||
if (rec.argumentToPureFunction) {
|
||||
var changeName = this.getChangeName(i);
|
||||
declarations.push(`${this.getLocalName(i)},${changeName}`);
|
||||
assignments.push(changeName);
|
||||
} else {
|
||||
declarations.push(`${this.getLocalName(i)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
var assignmentsCode =
|
||||
ListWrapper.isEmpty(assignments) ? '' : `${ListWrapper.join(assignments, '=')} = false;`;
|
||||
return `var ${ListWrapper.join(declarations, ',')};${assignmentsCode}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a statement initializing local variables for event handlers.
|
||||
*/
|
||||
genInitEventLocals(): string {
|
||||
var res = [`${this.getLocalName(CONTEXT_INDEX)} = ${this.getFieldName(CONTEXT_INDEX)}`];
|
||||
MapWrapper.forEach(this._sanitizedEventNames, (names, eb) => {
|
||||
for (var i = 0; i < names.length; ++i) {
|
||||
if (i !== CONTEXT_INDEX) {
|
||||
res.push(`${this.getEventLocalName(eb, i)}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
return res.length > 1 ? `var ${res.join(',')};` : '';
|
||||
}
|
||||
|
||||
getPreventDefaultAccesor(): string { return "preventDefault"; }
|
||||
|
||||
getFieldCount(): number { return this._sanitizedNames.length; }
|
||||
|
||||
getFieldName(idx: number): string { return this._addFieldPrefix(this._sanitizedNames[idx]); }
|
||||
|
||||
getAllFieldNames(): List<string> {
|
||||
var fieldList = [];
|
||||
for (var k = 0, kLen = this.getFieldCount(); k < kLen; ++k) {
|
||||
if (k === 0 || this.records[k - 1].shouldBeChecked()) {
|
||||
fieldList.push(this.getFieldName(k));
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0, iLen = this.records.length; i < iLen; ++i) {
|
||||
var rec = this.records[i];
|
||||
if (rec.isPipeRecord()) {
|
||||
fieldList.push(this.getPipeName(rec.selfIndex));
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0, jLen = this.directiveRecords.length; j < jLen; ++j) {
|
||||
var dRec = this.directiveRecords[j];
|
||||
fieldList.push(this.getDirectiveName(dRec.directiveIndex));
|
||||
if (!dRec.isDefaultChangeDetection()) {
|
||||
fieldList.push(this.getDetectorName(dRec.directiveIndex));
|
||||
}
|
||||
}
|
||||
return fieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates statements which clear all fields so that the change detector is dehydrated.
|
||||
*/
|
||||
genDehydrateFields(): string {
|
||||
var fields = this.getAllFieldNames();
|
||||
ListWrapper.removeAt(fields, CONTEXT_INDEX);
|
||||
if (ListWrapper.isEmpty(fields)) return '';
|
||||
|
||||
// At least one assignment.
|
||||
fields.push(`${this.utilName}.uninitialized;`);
|
||||
return ListWrapper.join(fields, ' = ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates statements destroying all pipe variables.
|
||||
*/
|
||||
genPipeOnDestroy(): string {
|
||||
return ListWrapper.join(
|
||||
ListWrapper.map(
|
||||
ListWrapper.filter(this.records, (r) => { return r.isPipeRecord(); }),
|
||||
(r) => {
|
||||
return `${this.utilName}.callPipeOnDestroy(${this.getPipeName(r.selfIndex)});`;
|
||||
}),
|
||||
'\n');
|
||||
}
|
||||
|
||||
getPipeName(idx: number): string {
|
||||
return this._addFieldPrefix(`${this._sanitizedNames[idx]}_pipe`);
|
||||
}
|
||||
|
||||
getDirectiveName(d: DirectiveIndex): string {
|
||||
return this._addFieldPrefix(`directive_${d.name}`);
|
||||
}
|
||||
|
||||
getDetectorName(d: DirectiveIndex): string { return this._addFieldPrefix(`detector_${d.name}`); }
|
||||
}
|
46
modules/angular2/src/core/change_detection/constants.ts
Normal file
46
modules/angular2/src/core/change_detection/constants.ts
Normal file
@ -0,0 +1,46 @@
|
||||
// TODO:vsavkin Use enums after switching to TypeScript
|
||||
import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
* CHECK_ONCE means that after calling detectChanges the mode of the change detector
|
||||
* will become CHECKED.
|
||||
*/
|
||||
export const CHECK_ONCE: string = "CHECK_ONCE";
|
||||
|
||||
/**
|
||||
* CHECKED means that the change detector should be skipped until its mode changes to
|
||||
* CHECK_ONCE or CHECK_ALWAYS.
|
||||
*/
|
||||
export const CHECKED: string = "CHECKED";
|
||||
|
||||
/**
|
||||
* CHECK_ALWAYS means that after calling detectChanges the mode of the change detector
|
||||
* will remain CHECK_ALWAYS.
|
||||
*/
|
||||
export const CHECK_ALWAYS: string = "ALWAYS_CHECK";
|
||||
|
||||
/**
|
||||
* DETACHED means that the change detector sub tree is not a part of the main tree and
|
||||
* should be skipped.
|
||||
*/
|
||||
export const DETACHED: string = "DETACHED";
|
||||
|
||||
/**
|
||||
* ON_PUSH means that the change detector's mode will be set to CHECK_ONCE during hydration.
|
||||
*/
|
||||
export const ON_PUSH: string = "ON_PUSH";
|
||||
|
||||
/**
|
||||
* DEFAULT means that the change detector's mode will be set to CHECK_ALWAYS during hydration.
|
||||
*/
|
||||
export const DEFAULT: string = "DEFAULT";
|
||||
|
||||
export function isDefaultChangeDetectionStrategy(changeDetectionStrategy: string): boolean {
|
||||
return isBlank(changeDetectionStrategy) || StringWrapper.equals(changeDetectionStrategy, DEFAULT);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is an experimental feature. Works only in Dart.
|
||||
*/
|
||||
export const ON_PUSH_OBSERVE = "ON_PUSH_OBSERVE";
|
@ -0,0 +1,620 @@
|
||||
import {CONST, BaseException} from 'angular2/src/facade/lang';
|
||||
import {
|
||||
isListLikeIterable,
|
||||
iterateListLike,
|
||||
ListWrapper,
|
||||
MapWrapper
|
||||
} from 'angular2/src/facade/collection';
|
||||
|
||||
import {
|
||||
isBlank,
|
||||
isPresent,
|
||||
stringify,
|
||||
getMapKey,
|
||||
looseIdentical,
|
||||
isArray
|
||||
} from 'angular2/src/facade/lang';
|
||||
|
||||
import {ChangeDetectorRef} from '../change_detector_ref';
|
||||
import {IterableDiffer, IterableDifferFactory} from '../differs/iterable_differs';
|
||||
|
||||
@CONST()
|
||||
export class DefaultIterableDifferFactory implements IterableDifferFactory {
|
||||
supports(obj: Object): boolean { return isListLikeIterable(obj); }
|
||||
create(cdRef: ChangeDetectorRef): any { return new DefaultIterableDiffer(); }
|
||||
}
|
||||
|
||||
export class DefaultIterableDiffer implements IterableDiffer {
|
||||
private _collection = null;
|
||||
private _length: number = null;
|
||||
// Keeps track of the used records at any point in time (during & across `_check()` calls)
|
||||
private _linkedRecords: _DuplicateMap = null;
|
||||
// Keeps track of the removed records at any point in time during `_check()` calls.
|
||||
private _unlinkedRecords: _DuplicateMap = null;
|
||||
private _previousItHead: CollectionChangeRecord = null;
|
||||
private _itHead: CollectionChangeRecord = null;
|
||||
private _itTail: CollectionChangeRecord = null;
|
||||
private _additionsHead: CollectionChangeRecord = null;
|
||||
private _additionsTail: CollectionChangeRecord = null;
|
||||
private _movesHead: CollectionChangeRecord = null;
|
||||
private _movesTail: CollectionChangeRecord = null;
|
||||
private _removalsHead: CollectionChangeRecord = null;
|
||||
private _removalsTail: CollectionChangeRecord = null;
|
||||
|
||||
get collection() { return this._collection; }
|
||||
|
||||
get length(): number { return this._length; }
|
||||
|
||||
forEachItem(fn: Function) {
|
||||
var record: CollectionChangeRecord;
|
||||
for (record = this._itHead; record !== null; record = record._next) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachPreviousItem(fn: Function) {
|
||||
var record: CollectionChangeRecord;
|
||||
for (record = this._previousItHead; record !== null; record = record._nextPrevious) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachAddedItem(fn: Function) {
|
||||
var record: CollectionChangeRecord;
|
||||
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachMovedItem(fn: Function) {
|
||||
var record: CollectionChangeRecord;
|
||||
for (record = this._movesHead; record !== null; record = record._nextMoved) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachRemovedItem(fn: Function) {
|
||||
var record: CollectionChangeRecord;
|
||||
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
diff(collection: any): DefaultIterableDiffer {
|
||||
if (isBlank(collection)) collection = [];
|
||||
if (!isListLikeIterable(collection)) {
|
||||
throw new BaseException(`Error trying to diff '${collection}'`);
|
||||
}
|
||||
|
||||
if (this.check(collection)) {
|
||||
return this;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy() {}
|
||||
|
||||
// todo(vicb): optim for UnmodifiableListView (frozen arrays)
|
||||
check(collection: any): boolean {
|
||||
this._reset();
|
||||
|
||||
var record: CollectionChangeRecord = this._itHead;
|
||||
var mayBeDirty: boolean = false;
|
||||
var index: number;
|
||||
var item;
|
||||
|
||||
if (isArray(collection)) {
|
||||
var list = collection;
|
||||
this._length = collection.length;
|
||||
|
||||
for (index = 0; index < this._length; index++) {
|
||||
item = list[index];
|
||||
if (record === null || !looseIdentical(record.item, item)) {
|
||||
record = this._mismatch(record, item, index);
|
||||
mayBeDirty = true;
|
||||
} else if (mayBeDirty) {
|
||||
// TODO(misko): can we limit this to duplicates only?
|
||||
record = this._verifyReinsertion(record, item, index);
|
||||
}
|
||||
record = record._next;
|
||||
}
|
||||
} else {
|
||||
index = 0;
|
||||
iterateListLike(collection, (item) => {
|
||||
if (record === null || !looseIdentical(record.item, item)) {
|
||||
record = this._mismatch(record, item, index);
|
||||
mayBeDirty = true;
|
||||
} else if (mayBeDirty) {
|
||||
// TODO(misko): can we limit this to duplicates only?
|
||||
record = this._verifyReinsertion(record, item, index);
|
||||
}
|
||||
record = record._next;
|
||||
index++;
|
||||
});
|
||||
this._length = index;
|
||||
}
|
||||
|
||||
this._truncate(record);
|
||||
this._collection = collection;
|
||||
return this.isDirty;
|
||||
}
|
||||
|
||||
// CollectionChanges is considered dirty if it has any additions, moves or removals.
|
||||
get isDirty(): boolean {
|
||||
return this._additionsHead !== null || this._movesHead !== null || this._removalsHead !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the state of the change objects to show no changes. This means set previousKey to
|
||||
* currentKey, and clear all of the queues (additions, moves, removals).
|
||||
* Set the previousIndexes of moved and added items to their currentIndexes
|
||||
* Reset the list of additions, moves and removals
|
||||
*/
|
||||
_reset() {
|
||||
if (this.isDirty) {
|
||||
var record: CollectionChangeRecord;
|
||||
var nextRecord: CollectionChangeRecord;
|
||||
|
||||
for (record = this._previousItHead = this._itHead; record !== null; record = record._next) {
|
||||
record._nextPrevious = record._next;
|
||||
}
|
||||
|
||||
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
|
||||
record.previousIndex = record.currentIndex;
|
||||
}
|
||||
this._additionsHead = this._additionsTail = null;
|
||||
|
||||
for (record = this._movesHead; record !== null; record = nextRecord) {
|
||||
record.previousIndex = record.currentIndex;
|
||||
nextRecord = record._nextMoved;
|
||||
}
|
||||
this._movesHead = this._movesTail = null;
|
||||
this._removalsHead = this._removalsTail = null;
|
||||
|
||||
// todo(vicb) when assert gets supported
|
||||
// assert(!this.isDirty);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the core function which handles differences between collections.
|
||||
*
|
||||
* - `record` is the record which we saw at this position last time. If null then it is a new
|
||||
* item.
|
||||
* - `item` is the current item in the collection
|
||||
* - `index` is the position of the item in the collection
|
||||
*/
|
||||
_mismatch(record: CollectionChangeRecord, item, index: number): CollectionChangeRecord {
|
||||
// The previous record after which we will append the current one.
|
||||
var previousRecord: CollectionChangeRecord;
|
||||
|
||||
if (record === null) {
|
||||
previousRecord = this._itTail;
|
||||
} else {
|
||||
previousRecord = record._prev;
|
||||
// Remove the record from the collection since we know it does not match the item.
|
||||
this._remove(record);
|
||||
}
|
||||
|
||||
// Attempt to see if we have seen the item before.
|
||||
record = this._linkedRecords === null ? null : this._linkedRecords.get(item, index);
|
||||
if (record !== null) {
|
||||
// We have seen this before, we need to move it forward in the collection.
|
||||
this._moveAfter(record, previousRecord, index);
|
||||
} else {
|
||||
// Never seen it, check evicted list.
|
||||
record = this._unlinkedRecords === null ? null : this._unlinkedRecords.get(item);
|
||||
if (record !== null) {
|
||||
// It is an item which we have evicted earlier: reinsert it back into the list.
|
||||
this._reinsertAfter(record, previousRecord, index);
|
||||
} else {
|
||||
// It is a new item: add it.
|
||||
record = this._addAfter(new CollectionChangeRecord(item), previousRecord, index);
|
||||
}
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* This check is only needed if an array contains duplicates. (Short circuit of nothing dirty)
|
||||
*
|
||||
* Use case: `[a, a]` => `[b, a, a]`
|
||||
*
|
||||
* If we did not have this check then the insertion of `b` would:
|
||||
* 1) evict first `a`
|
||||
* 2) insert `b` at `0` index.
|
||||
* 3) leave `a` at index `1` as is. <-- this is wrong!
|
||||
* 3) reinsert `a` at index 2. <-- this is wrong!
|
||||
*
|
||||
* The correct behavior is:
|
||||
* 1) evict first `a`
|
||||
* 2) insert `b` at `0` index.
|
||||
* 3) reinsert `a` at index 1.
|
||||
* 3) move `a` at from `1` to `2`.
|
||||
*
|
||||
*
|
||||
* Double check that we have not evicted a duplicate item. We need to check if the item type may
|
||||
* have already been removed:
|
||||
* The insertion of b will evict the first 'a'. If we don't reinsert it now it will be reinserted
|
||||
* at the end. Which will show up as the two 'a's switching position. This is incorrect, since a
|
||||
* better way to think of it is as insert of 'b' rather then switch 'a' with 'b' and then add 'a'
|
||||
* at the end.
|
||||
*/
|
||||
_verifyReinsertion(record: CollectionChangeRecord, item, index: number): CollectionChangeRecord {
|
||||
var reinsertRecord: CollectionChangeRecord =
|
||||
this._unlinkedRecords === null ? null : this._unlinkedRecords.get(item);
|
||||
if (reinsertRecord !== null) {
|
||||
record = this._reinsertAfter(reinsertRecord, record._prev, index);
|
||||
} else if (record.currentIndex != index) {
|
||||
record.currentIndex = index;
|
||||
this._addToMoves(record, index);
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rid of any excess {@link CollectionChangeRecord}s from the previous collection
|
||||
*
|
||||
* - `record` The first excess {@link CollectionChangeRecord}.
|
||||
*/
|
||||
_truncate(record: CollectionChangeRecord) {
|
||||
// Anything after that needs to be removed;
|
||||
while (record !== null) {
|
||||
var nextRecord: CollectionChangeRecord = record._next;
|
||||
this._addToRemovals(this._unlink(record));
|
||||
record = nextRecord;
|
||||
}
|
||||
if (this._unlinkedRecords !== null) {
|
||||
this._unlinkedRecords.clear();
|
||||
}
|
||||
|
||||
if (this._additionsTail !== null) {
|
||||
this._additionsTail._nextAdded = null;
|
||||
}
|
||||
if (this._movesTail !== null) {
|
||||
this._movesTail._nextMoved = null;
|
||||
}
|
||||
if (this._itTail !== null) {
|
||||
this._itTail._next = null;
|
||||
}
|
||||
if (this._removalsTail !== null) {
|
||||
this._removalsTail._nextRemoved = null;
|
||||
}
|
||||
}
|
||||
|
||||
_reinsertAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
|
||||
index: number): CollectionChangeRecord {
|
||||
if (this._unlinkedRecords !== null) {
|
||||
this._unlinkedRecords.remove(record);
|
||||
}
|
||||
var prev = record._prevRemoved;
|
||||
var next = record._nextRemoved;
|
||||
|
||||
if (prev === null) {
|
||||
this._removalsHead = next;
|
||||
} else {
|
||||
prev._nextRemoved = next;
|
||||
}
|
||||
if (next === null) {
|
||||
this._removalsTail = prev;
|
||||
} else {
|
||||
next._prevRemoved = prev;
|
||||
}
|
||||
|
||||
this._insertAfter(record, prevRecord, index);
|
||||
this._addToMoves(record, index);
|
||||
return record;
|
||||
}
|
||||
|
||||
_moveAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
|
||||
index: number): CollectionChangeRecord {
|
||||
this._unlink(record);
|
||||
this._insertAfter(record, prevRecord, index);
|
||||
this._addToMoves(record, index);
|
||||
return record;
|
||||
}
|
||||
|
||||
_addAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
|
||||
index: number): CollectionChangeRecord {
|
||||
this._insertAfter(record, prevRecord, index);
|
||||
|
||||
if (this._additionsTail === null) {
|
||||
// todo(vicb)
|
||||
// assert(this._additionsHead === null);
|
||||
this._additionsTail = this._additionsHead = record;
|
||||
} else {
|
||||
// todo(vicb)
|
||||
// assert(_additionsTail._nextAdded === null);
|
||||
// assert(record._nextAdded === null);
|
||||
this._additionsTail = this._additionsTail._nextAdded = record;
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
_insertAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
|
||||
index: number): CollectionChangeRecord {
|
||||
// todo(vicb)
|
||||
// assert(record != prevRecord);
|
||||
// assert(record._next === null);
|
||||
// assert(record._prev === null);
|
||||
|
||||
var next: CollectionChangeRecord = prevRecord === null ? this._itHead : prevRecord._next;
|
||||
// todo(vicb)
|
||||
// assert(next != record);
|
||||
// assert(prevRecord != record);
|
||||
record._next = next;
|
||||
record._prev = prevRecord;
|
||||
if (next === null) {
|
||||
this._itTail = record;
|
||||
} else {
|
||||
next._prev = record;
|
||||
}
|
||||
if (prevRecord === null) {
|
||||
this._itHead = record;
|
||||
} else {
|
||||
prevRecord._next = record;
|
||||
}
|
||||
|
||||
if (this._linkedRecords === null) {
|
||||
this._linkedRecords = new _DuplicateMap();
|
||||
}
|
||||
this._linkedRecords.put(record);
|
||||
|
||||
record.currentIndex = index;
|
||||
return record;
|
||||
}
|
||||
|
||||
_remove(record: CollectionChangeRecord): CollectionChangeRecord {
|
||||
return this._addToRemovals(this._unlink(record));
|
||||
}
|
||||
|
||||
_unlink(record: CollectionChangeRecord): CollectionChangeRecord {
|
||||
if (this._linkedRecords !== null) {
|
||||
this._linkedRecords.remove(record);
|
||||
}
|
||||
|
||||
var prev = record._prev;
|
||||
var next = record._next;
|
||||
|
||||
// todo(vicb)
|
||||
// assert((record._prev = null) === null);
|
||||
// assert((record._next = null) === null);
|
||||
|
||||
if (prev === null) {
|
||||
this._itHead = next;
|
||||
} else {
|
||||
prev._next = next;
|
||||
}
|
||||
if (next === null) {
|
||||
this._itTail = prev;
|
||||
} else {
|
||||
next._prev = prev;
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
_addToMoves(record: CollectionChangeRecord, toIndex: number): CollectionChangeRecord {
|
||||
// todo(vicb)
|
||||
// assert(record._nextMoved === null);
|
||||
|
||||
if (record.previousIndex === toIndex) {
|
||||
return record;
|
||||
}
|
||||
|
||||
if (this._movesTail === null) {
|
||||
// todo(vicb)
|
||||
// assert(_movesHead === null);
|
||||
this._movesTail = this._movesHead = record;
|
||||
} else {
|
||||
// todo(vicb)
|
||||
// assert(_movesTail._nextMoved === null);
|
||||
this._movesTail = this._movesTail._nextMoved = record;
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
_addToRemovals(record: CollectionChangeRecord): CollectionChangeRecord {
|
||||
if (this._unlinkedRecords === null) {
|
||||
this._unlinkedRecords = new _DuplicateMap();
|
||||
}
|
||||
this._unlinkedRecords.put(record);
|
||||
record.currentIndex = null;
|
||||
record._nextRemoved = null;
|
||||
|
||||
if (this._removalsTail === null) {
|
||||
// todo(vicb)
|
||||
// assert(_removalsHead === null);
|
||||
this._removalsTail = this._removalsHead = record;
|
||||
record._prevRemoved = null;
|
||||
} else {
|
||||
// todo(vicb)
|
||||
// assert(_removalsTail._nextRemoved === null);
|
||||
// assert(record._nextRemoved === null);
|
||||
record._prevRemoved = this._removalsTail;
|
||||
this._removalsTail = this._removalsTail._nextRemoved = record;
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
var record: CollectionChangeRecord;
|
||||
|
||||
var list = [];
|
||||
for (record = this._itHead; record !== null; record = record._next) {
|
||||
list.push(record);
|
||||
}
|
||||
|
||||
var previous = [];
|
||||
for (record = this._previousItHead; record !== null; record = record._nextPrevious) {
|
||||
previous.push(record);
|
||||
}
|
||||
|
||||
var additions = [];
|
||||
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
|
||||
additions.push(record);
|
||||
}
|
||||
var moves = [];
|
||||
for (record = this._movesHead; record !== null; record = record._nextMoved) {
|
||||
moves.push(record);
|
||||
}
|
||||
|
||||
var removals = [];
|
||||
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
|
||||
removals.push(record);
|
||||
}
|
||||
|
||||
return "collection: " + list.join(', ') + "\n" + "previous: " + previous.join(', ') + "\n" +
|
||||
"additions: " + additions.join(', ') + "\n" + "moves: " + moves.join(', ') + "\n" +
|
||||
"removals: " + removals.join(', ') + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionChangeRecord {
|
||||
currentIndex: number = null;
|
||||
previousIndex: number = null;
|
||||
|
||||
_nextPrevious: CollectionChangeRecord = null;
|
||||
_prev: CollectionChangeRecord = null;
|
||||
_next: CollectionChangeRecord = null;
|
||||
_prevDup: CollectionChangeRecord = null;
|
||||
_nextDup: CollectionChangeRecord = null;
|
||||
_prevRemoved: CollectionChangeRecord = null;
|
||||
_nextRemoved: CollectionChangeRecord = null;
|
||||
_nextAdded: CollectionChangeRecord = null;
|
||||
_nextMoved: CollectionChangeRecord = null;
|
||||
|
||||
constructor(public item: any) {}
|
||||
|
||||
toString(): string {
|
||||
return this.previousIndex === this.currentIndex ?
|
||||
stringify(this.item) :
|
||||
stringify(this.item) + '[' + stringify(this.previousIndex) + '->' +
|
||||
stringify(this.currentIndex) + ']';
|
||||
}
|
||||
}
|
||||
|
||||
// A linked list of CollectionChangeRecords with the same CollectionChangeRecord.item
|
||||
class _DuplicateItemRecordList {
|
||||
_head: CollectionChangeRecord = null;
|
||||
_tail: CollectionChangeRecord = null;
|
||||
|
||||
/**
|
||||
* Append the record to the list of duplicates.
|
||||
*
|
||||
* Note: by design all records in the list of duplicates hold the same value in record.item.
|
||||
*/
|
||||
add(record: CollectionChangeRecord): void {
|
||||
if (this._head === null) {
|
||||
this._head = this._tail = record;
|
||||
record._nextDup = null;
|
||||
record._prevDup = null;
|
||||
} else {
|
||||
// todo(vicb)
|
||||
// assert(record.item == _head.item ||
|
||||
// record.item is num && record.item.isNaN && _head.item is num && _head.item.isNaN);
|
||||
this._tail._nextDup = record;
|
||||
record._prevDup = this._tail;
|
||||
record._nextDup = null;
|
||||
this._tail = record;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a CollectionChangeRecord having CollectionChangeRecord.item == item and
|
||||
// CollectionChangeRecord.currentIndex >= afterIndex
|
||||
get(item: any, afterIndex: number): CollectionChangeRecord {
|
||||
var record: CollectionChangeRecord;
|
||||
for (record = this._head; record !== null; record = record._nextDup) {
|
||||
if ((afterIndex === null || afterIndex < record.currentIndex) &&
|
||||
looseIdentical(record.item, item)) {
|
||||
return record;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove one {@link CollectionChangeRecord} from the list of duplicates.
|
||||
*
|
||||
* Returns whether the list of duplicates is empty.
|
||||
*/
|
||||
remove(record: CollectionChangeRecord): boolean {
|
||||
// todo(vicb)
|
||||
// assert(() {
|
||||
// // verify that the record being removed is in the list.
|
||||
// for (CollectionChangeRecord cursor = _head; cursor != null; cursor = cursor._nextDup) {
|
||||
// if (identical(cursor, record)) return true;
|
||||
// }
|
||||
// return false;
|
||||
//});
|
||||
|
||||
var prev: CollectionChangeRecord = record._prevDup;
|
||||
var next: CollectionChangeRecord = record._nextDup;
|
||||
if (prev === null) {
|
||||
this._head = next;
|
||||
} else {
|
||||
prev._nextDup = next;
|
||||
}
|
||||
if (next === null) {
|
||||
this._tail = prev;
|
||||
} else {
|
||||
next._prevDup = prev;
|
||||
}
|
||||
return this._head === null;
|
||||
}
|
||||
}
|
||||
|
||||
class _DuplicateMap {
|
||||
map: Map<any, _DuplicateItemRecordList> = new Map();
|
||||
|
||||
put(record: CollectionChangeRecord) {
|
||||
// todo(vicb) handle corner cases
|
||||
var key = getMapKey(record.item);
|
||||
|
||||
var duplicates = this.map.get(key);
|
||||
if (!isPresent(duplicates)) {
|
||||
duplicates = new _DuplicateItemRecordList();
|
||||
this.map.set(key, duplicates);
|
||||
}
|
||||
duplicates.add(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the `value` using key. Because the CollectionChangeRecord value maybe one which we
|
||||
* have already iterated over, we use the afterIndex to pretend it is not there.
|
||||
*
|
||||
* Use case: `[a, b, c, a, a]` if we are at index `3` which is the second `a` then asking if we
|
||||
* have any more `a`s needs to return the last `a` not the first or second.
|
||||
*/
|
||||
get(value: any, afterIndex: number = null): CollectionChangeRecord {
|
||||
var key = getMapKey(value);
|
||||
|
||||
var recordList = this.map.get(key);
|
||||
return isBlank(recordList) ? null : recordList.get(value, afterIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link CollectionChangeRecord} from the list of duplicates.
|
||||
*
|
||||
* The list of duplicates also is removed from the map if it gets empty.
|
||||
*/
|
||||
remove(record: CollectionChangeRecord): CollectionChangeRecord {
|
||||
var key = getMapKey(record.item);
|
||||
// todo(vicb)
|
||||
// assert(this.map.containsKey(key));
|
||||
var recordList: _DuplicateItemRecordList = this.map.get(key);
|
||||
// Remove the list of duplicates when it gets empty
|
||||
if (recordList.remove(record)) {
|
||||
MapWrapper.delete(this.map, key);
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
get isEmpty(): boolean { return MapWrapper.size(this.map) === 0; }
|
||||
|
||||
clear() { this.map.clear(); }
|
||||
|
||||
toString(): string { return '_DuplicateMap(' + stringify(this.map) + ')'; }
|
||||
}
|
@ -0,0 +1,353 @@
|
||||
import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {
|
||||
stringify,
|
||||
looseIdentical,
|
||||
isJsObject,
|
||||
CONST,
|
||||
isBlank,
|
||||
BaseException
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {ChangeDetectorRef} from '../change_detector_ref';
|
||||
import {KeyValueDiffer, KeyValueDifferFactory} from '../differs/keyvalue_differs';
|
||||
|
||||
@CONST()
|
||||
export class DefaultKeyValueDifferFactory implements KeyValueDifferFactory {
|
||||
supports(obj: any): boolean { return obj instanceof Map || isJsObject(obj); }
|
||||
|
||||
create(cdRef: ChangeDetectorRef): KeyValueDiffer { return new DefaultKeyValueDiffer(); }
|
||||
}
|
||||
|
||||
export class DefaultKeyValueDiffer implements KeyValueDiffer {
|
||||
private _records: Map<any, any> = new Map();
|
||||
private _mapHead: KVChangeRecord = null;
|
||||
private _previousMapHead: KVChangeRecord = null;
|
||||
private _changesHead: KVChangeRecord = null;
|
||||
private _changesTail: KVChangeRecord = null;
|
||||
private _additionsHead: KVChangeRecord = null;
|
||||
private _additionsTail: KVChangeRecord = null;
|
||||
private _removalsHead: KVChangeRecord = null;
|
||||
private _removalsTail: KVChangeRecord = null;
|
||||
|
||||
get isDirty(): boolean {
|
||||
return this._additionsHead !== null || this._changesHead !== null ||
|
||||
this._removalsHead !== null;
|
||||
}
|
||||
|
||||
forEachItem(fn: Function) {
|
||||
var record: KVChangeRecord;
|
||||
for (record = this._mapHead; record !== null; record = record._next) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachPreviousItem(fn: Function) {
|
||||
var record: KVChangeRecord;
|
||||
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachChangedItem(fn: Function) {
|
||||
var record: KVChangeRecord;
|
||||
for (record = this._changesHead; record !== null; record = record._nextChanged) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachAddedItem(fn: Function) {
|
||||
var record: KVChangeRecord;
|
||||
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachRemovedItem(fn: Function) {
|
||||
var record: KVChangeRecord;
|
||||
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
diff(map: Map<any, any>): any {
|
||||
if (isBlank(map)) map = MapWrapper.createFromPairs([]);
|
||||
if (!(map instanceof Map || isJsObject(map))) {
|
||||
throw new BaseException(`Error trying to diff '${map}'`);
|
||||
}
|
||||
|
||||
if (this.check(map)) {
|
||||
return this;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy() {}
|
||||
|
||||
check(map: Map<any, any>): boolean {
|
||||
this._reset();
|
||||
var records = this._records;
|
||||
var oldSeqRecord: KVChangeRecord = this._mapHead;
|
||||
var lastOldSeqRecord: KVChangeRecord = null;
|
||||
var lastNewSeqRecord: KVChangeRecord = null;
|
||||
var seqChanged: boolean = false;
|
||||
|
||||
this._forEach(map, (value, key) => {
|
||||
var newSeqRecord;
|
||||
if (oldSeqRecord !== null && key === oldSeqRecord.key) {
|
||||
newSeqRecord = oldSeqRecord;
|
||||
if (!looseIdentical(value, oldSeqRecord.currentValue)) {
|
||||
oldSeqRecord.previousValue = oldSeqRecord.currentValue;
|
||||
oldSeqRecord.currentValue = value;
|
||||
this._addToChanges(oldSeqRecord);
|
||||
}
|
||||
} else {
|
||||
seqChanged = true;
|
||||
if (oldSeqRecord !== null) {
|
||||
oldSeqRecord._next = null;
|
||||
this._removeFromSeq(lastOldSeqRecord, oldSeqRecord);
|
||||
this._addToRemovals(oldSeqRecord);
|
||||
}
|
||||
if (records.has(key)) {
|
||||
newSeqRecord = records.get(key);
|
||||
} else {
|
||||
newSeqRecord = new KVChangeRecord(key);
|
||||
records.set(key, newSeqRecord);
|
||||
newSeqRecord.currentValue = value;
|
||||
this._addToAdditions(newSeqRecord);
|
||||
}
|
||||
}
|
||||
|
||||
if (seqChanged) {
|
||||
if (this._isInRemovals(newSeqRecord)) {
|
||||
this._removeFromRemovals(newSeqRecord);
|
||||
}
|
||||
if (lastNewSeqRecord == null) {
|
||||
this._mapHead = newSeqRecord;
|
||||
} else {
|
||||
lastNewSeqRecord._next = newSeqRecord;
|
||||
}
|
||||
}
|
||||
lastOldSeqRecord = oldSeqRecord;
|
||||
lastNewSeqRecord = newSeqRecord;
|
||||
oldSeqRecord = oldSeqRecord === null ? null : oldSeqRecord._next;
|
||||
});
|
||||
this._truncate(lastOldSeqRecord, oldSeqRecord);
|
||||
return this.isDirty;
|
||||
}
|
||||
|
||||
_reset() {
|
||||
if (this.isDirty) {
|
||||
var record: KVChangeRecord;
|
||||
// Record the state of the mapping
|
||||
for (record = this._previousMapHead = this._mapHead; record !== null; record = record._next) {
|
||||
record._nextPrevious = record._next;
|
||||
}
|
||||
|
||||
for (record = this._changesHead; record !== null; record = record._nextChanged) {
|
||||
record.previousValue = record.currentValue;
|
||||
}
|
||||
|
||||
for (record = this._additionsHead; record != null; record = record._nextAdded) {
|
||||
record.previousValue = record.currentValue;
|
||||
}
|
||||
|
||||
// todo(vicb) once assert is supported
|
||||
// assert(() {
|
||||
// var r = _changesHead;
|
||||
// while (r != null) {
|
||||
// var nextRecord = r._nextChanged;
|
||||
// r._nextChanged = null;
|
||||
// r = nextRecord;
|
||||
// }
|
||||
//
|
||||
// r = _additionsHead;
|
||||
// while (r != null) {
|
||||
// var nextRecord = r._nextAdded;
|
||||
// r._nextAdded = null;
|
||||
// r = nextRecord;
|
||||
// }
|
||||
//
|
||||
// r = _removalsHead;
|
||||
// while (r != null) {
|
||||
// var nextRecord = r._nextRemoved;
|
||||
// r._nextRemoved = null;
|
||||
// r = nextRecord;
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
//});
|
||||
this._changesHead = this._changesTail = null;
|
||||
this._additionsHead = this._additionsTail = null;
|
||||
this._removalsHead = this._removalsTail = null;
|
||||
}
|
||||
}
|
||||
|
||||
_truncate(lastRecord: KVChangeRecord, record: KVChangeRecord) {
|
||||
while (record !== null) {
|
||||
if (lastRecord === null) {
|
||||
this._mapHead = null;
|
||||
} else {
|
||||
lastRecord._next = null;
|
||||
}
|
||||
var nextRecord = record._next;
|
||||
// todo(vicb) assert
|
||||
// assert((() {
|
||||
// record._next = null;
|
||||
// return true;
|
||||
//}));
|
||||
this._addToRemovals(record);
|
||||
lastRecord = record;
|
||||
record = nextRecord;
|
||||
}
|
||||
|
||||
for (var rec: KVChangeRecord = this._removalsHead; rec !== null; rec = rec._nextRemoved) {
|
||||
rec.previousValue = rec.currentValue;
|
||||
rec.currentValue = null;
|
||||
MapWrapper.delete(this._records, rec.key);
|
||||
}
|
||||
}
|
||||
|
||||
_isInRemovals(record: KVChangeRecord) {
|
||||
return record === this._removalsHead || record._nextRemoved !== null ||
|
||||
record._prevRemoved !== null;
|
||||
}
|
||||
|
||||
_addToRemovals(record: KVChangeRecord) {
|
||||
// todo(vicb) assert
|
||||
// assert(record._next == null);
|
||||
// assert(record._nextAdded == null);
|
||||
// assert(record._nextChanged == null);
|
||||
// assert(record._nextRemoved == null);
|
||||
// assert(record._prevRemoved == null);
|
||||
if (this._removalsHead === null) {
|
||||
this._removalsHead = this._removalsTail = record;
|
||||
} else {
|
||||
this._removalsTail._nextRemoved = record;
|
||||
record._prevRemoved = this._removalsTail;
|
||||
this._removalsTail = record;
|
||||
}
|
||||
}
|
||||
|
||||
_removeFromSeq(prev: KVChangeRecord, record: KVChangeRecord) {
|
||||
var next = record._next;
|
||||
if (prev === null) {
|
||||
this._mapHead = next;
|
||||
} else {
|
||||
prev._next = next;
|
||||
}
|
||||
// todo(vicb) assert
|
||||
// assert((() {
|
||||
// record._next = null;
|
||||
// return true;
|
||||
//})());
|
||||
}
|
||||
|
||||
_removeFromRemovals(record: KVChangeRecord) {
|
||||
// todo(vicb) assert
|
||||
// assert(record._next == null);
|
||||
// assert(record._nextAdded == null);
|
||||
// assert(record._nextChanged == null);
|
||||
|
||||
var prev = record._prevRemoved;
|
||||
var next = record._nextRemoved;
|
||||
if (prev === null) {
|
||||
this._removalsHead = next;
|
||||
} else {
|
||||
prev._nextRemoved = next;
|
||||
}
|
||||
if (next === null) {
|
||||
this._removalsTail = prev;
|
||||
} else {
|
||||
next._prevRemoved = prev;
|
||||
}
|
||||
record._prevRemoved = record._nextRemoved = null;
|
||||
}
|
||||
|
||||
_addToAdditions(record: KVChangeRecord) {
|
||||
// todo(vicb): assert
|
||||
// assert(record._next == null);
|
||||
// assert(record._nextAdded == null);
|
||||
// assert(record._nextChanged == null);
|
||||
// assert(record._nextRemoved == null);
|
||||
// assert(record._prevRemoved == null);
|
||||
if (this._additionsHead === null) {
|
||||
this._additionsHead = this._additionsTail = record;
|
||||
} else {
|
||||
this._additionsTail._nextAdded = record;
|
||||
this._additionsTail = record;
|
||||
}
|
||||
}
|
||||
|
||||
_addToChanges(record: KVChangeRecord) {
|
||||
// todo(vicb) assert
|
||||
// assert(record._nextAdded == null);
|
||||
// assert(record._nextChanged == null);
|
||||
// assert(record._nextRemoved == null);
|
||||
// assert(record._prevRemoved == null);
|
||||
if (this._changesHead === null) {
|
||||
this._changesHead = this._changesTail = record;
|
||||
} else {
|
||||
this._changesTail._nextChanged = record;
|
||||
this._changesTail = record;
|
||||
}
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
var items = [];
|
||||
var previous = [];
|
||||
var changes = [];
|
||||
var additions = [];
|
||||
var removals = [];
|
||||
var record: KVChangeRecord;
|
||||
|
||||
for (record = this._mapHead; record !== null; record = record._next) {
|
||||
items.push(stringify(record));
|
||||
}
|
||||
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
|
||||
previous.push(stringify(record));
|
||||
}
|
||||
for (record = this._changesHead; record !== null; record = record._nextChanged) {
|
||||
changes.push(stringify(record));
|
||||
}
|
||||
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
|
||||
additions.push(stringify(record));
|
||||
}
|
||||
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
|
||||
removals.push(stringify(record));
|
||||
}
|
||||
|
||||
return "map: " + items.join(', ') + "\n" + "previous: " + previous.join(', ') + "\n" +
|
||||
"additions: " + additions.join(', ') + "\n" + "changes: " + changes.join(', ') + "\n" +
|
||||
"removals: " + removals.join(', ') + "\n";
|
||||
}
|
||||
|
||||
_forEach(obj, fn: Function) {
|
||||
if (obj instanceof Map) {
|
||||
MapWrapper.forEach(obj, fn);
|
||||
} else {
|
||||
StringMapWrapper.forEach(obj, fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class KVChangeRecord {
|
||||
previousValue: any = null;
|
||||
currentValue: any = null;
|
||||
|
||||
_nextPrevious: KVChangeRecord = null;
|
||||
_next: KVChangeRecord = null;
|
||||
_nextAdded: KVChangeRecord = null;
|
||||
_nextRemoved: KVChangeRecord = null;
|
||||
_prevRemoved: KVChangeRecord = null;
|
||||
_nextChanged: KVChangeRecord = null;
|
||||
|
||||
constructor(public key: any) {}
|
||||
|
||||
toString(): string {
|
||||
return looseIdentical(this.previousValue, this.currentValue) ?
|
||||
stringify(this.key) :
|
||||
(stringify(this.key) + '[' + stringify(this.previousValue) + '->' +
|
||||
stringify(this.currentValue) + ']');
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {ChangeDetectorRef} from '../change_detector_ref';
|
||||
import {Binding, SkipSelfMetadata, OptionalMetadata, Injectable} from 'angular2/di';
|
||||
|
||||
export interface IterableDiffer {
|
||||
diff(object: Object): any;
|
||||
onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a factory for {@link IterableDiffer}.
|
||||
*/
|
||||
export interface IterableDifferFactory {
|
||||
supports(objects: Object): boolean;
|
||||
create(cdRef: ChangeDetectorRef): IterableDiffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* A repository of different iterable diffing strategies used by NgFor, NgClass, and others.
|
||||
*/
|
||||
@Injectable()
|
||||
@CONST()
|
||||
export class IterableDiffers {
|
||||
constructor(public factories: IterableDifferFactory[]) {}
|
||||
|
||||
static create(factories: IterableDifferFactory[], parent?: IterableDiffers): IterableDiffers {
|
||||
if (isPresent(parent)) {
|
||||
var copied = ListWrapper.clone(parent.factories);
|
||||
factories = factories.concat(copied);
|
||||
return new IterableDiffers(factories);
|
||||
} else {
|
||||
return new IterableDiffers(factories);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an array of {@link IterableDifferFactory} and returns a binding used to extend the
|
||||
* inherited {@link IterableDiffers} instance with the provided factories and return a new
|
||||
* {@link IterableDiffers} instance.
|
||||
*
|
||||
* The following example shows how to extend an existing list of factories,
|
||||
* which will only be applied to the injector for this component and its children.
|
||||
* This step is all that's required to make a new {@link IterableDiffer} available.
|
||||
*
|
||||
* # Example
|
||||
*
|
||||
* ```
|
||||
* @Component({
|
||||
* viewBindings: [
|
||||
* IterableDiffers.extend([new ImmutableListDiffer()])
|
||||
* ]
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
static extend(factories: IterableDifferFactory[]): Binding {
|
||||
return new Binding(IterableDiffers, {
|
||||
toFactory: (parent: IterableDiffers) => {
|
||||
if (isBlank(parent)) {
|
||||
// Typically would occur when calling IterableDiffers.extend inside of dependencies passed
|
||||
// to
|
||||
// bootstrap(), which would override default pipes instead of extending them.
|
||||
throw new BaseException('Cannot extend IterableDiffers without a parent injector');
|
||||
}
|
||||
return IterableDiffers.create(factories, parent);
|
||||
},
|
||||
// Dependency technically isn't optional, but we can provide a better error message this way.
|
||||
deps: [[IterableDiffers, new SkipSelfMetadata(), new OptionalMetadata()]]
|
||||
});
|
||||
}
|
||||
|
||||
find(iterable: Object): IterableDifferFactory {
|
||||
var factory = ListWrapper.find(this.factories, f => f.supports(iterable));
|
||||
if (isPresent(factory)) {
|
||||
return factory;
|
||||
} else {
|
||||
throw new BaseException(`Cannot find a differ supporting object '${iterable}'`);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {ChangeDetectorRef} from '../change_detector_ref';
|
||||
import {Binding, SkipSelfMetadata, OptionalMetadata, Injectable} from 'angular2/di';
|
||||
|
||||
export interface KeyValueDiffer {
|
||||
diff(object: Object);
|
||||
onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a factory for {@link KeyValueDiffer}.
|
||||
*/
|
||||
export interface KeyValueDifferFactory {
|
||||
supports(objects: Object): boolean;
|
||||
create(cdRef: ChangeDetectorRef): KeyValueDiffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* A repository of different Map diffing strategies used by NgClass, NgStyle, and others.
|
||||
*/
|
||||
@Injectable()
|
||||
@CONST()
|
||||
export class KeyValueDiffers {
|
||||
constructor(public factories: KeyValueDifferFactory[]) {}
|
||||
|
||||
static create(factories: KeyValueDifferFactory[], parent?: KeyValueDiffers): KeyValueDiffers {
|
||||
if (isPresent(parent)) {
|
||||
var copied = ListWrapper.clone(parent.factories);
|
||||
factories = factories.concat(copied);
|
||||
return new KeyValueDiffers(factories);
|
||||
} else {
|
||||
return new KeyValueDiffers(factories);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an array of {@link KeyValueDifferFactory} and returns a binding used to extend the
|
||||
* inherited {@link KeyValueDiffers} instance with the provided factories and return a new
|
||||
* {@link KeyValueDiffers} instance.
|
||||
*
|
||||
* The following example shows how to extend an existing list of factories,
|
||||
* which will only be applied to the injector for this component and its children.
|
||||
* This step is all that's required to make a new {@link KeyValueDiffer} available.
|
||||
*
|
||||
* # Example
|
||||
*
|
||||
* ```
|
||||
* @Component({
|
||||
* viewBindings: [
|
||||
* KeyValueDiffers.extend([new ImmutableMapDiffer()])
|
||||
* ]
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
static extend(factories: KeyValueDifferFactory[]): Binding {
|
||||
return new Binding(KeyValueDiffers, {
|
||||
toFactory: (parent: KeyValueDiffers) => {
|
||||
if (isBlank(parent)) {
|
||||
// Typically would occur when calling KeyValueDiffers.extend inside of dependencies passed
|
||||
// to
|
||||
// bootstrap(), which would override default pipes instead of extending them.
|
||||
throw new BaseException('Cannot extend KeyValueDiffers without a parent injector');
|
||||
}
|
||||
return KeyValueDiffers.create(factories, parent);
|
||||
},
|
||||
// Dependency technically isn't optional, but we can provide a better error message this way.
|
||||
deps: [[KeyValueDiffers, new SkipSelfMetadata(), new OptionalMetadata()]]
|
||||
});
|
||||
}
|
||||
|
||||
find(kv: Object): KeyValueDifferFactory {
|
||||
var factory = ListWrapper.find(this.factories, f => f.supports(kv));
|
||||
if (isPresent(factory)) {
|
||||
return factory;
|
||||
} else {
|
||||
throw new BaseException(`Cannot find a differ supporting object '${kv}'`);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/facade/lang';
|
||||
import {isDefaultChangeDetectionStrategy} from './constants';
|
||||
|
||||
export class DirectiveIndex {
|
||||
constructor(public elementIndex: number, public directiveIndex: number) {}
|
||||
|
||||
get name() { return `${this.elementIndex}_${this.directiveIndex}`; }
|
||||
}
|
||||
|
||||
export class DirectiveRecord {
|
||||
directiveIndex: DirectiveIndex;
|
||||
callOnAllChangesDone: boolean;
|
||||
callOnChange: boolean;
|
||||
callOnCheck: boolean;
|
||||
callOnInit: boolean;
|
||||
changeDetection: string;
|
||||
|
||||
constructor({directiveIndex, callOnAllChangesDone, callOnChange, callOnCheck, callOnInit,
|
||||
changeDetection}: {
|
||||
directiveIndex?: DirectiveIndex,
|
||||
callOnAllChangesDone?: boolean,
|
||||
callOnChange?: boolean,
|
||||
callOnCheck?: boolean,
|
||||
callOnInit?: boolean,
|
||||
changeDetection?: string
|
||||
} = {}) {
|
||||
this.directiveIndex = directiveIndex;
|
||||
this.callOnAllChangesDone = normalizeBool(callOnAllChangesDone);
|
||||
this.callOnChange = normalizeBool(callOnChange);
|
||||
this.callOnCheck = normalizeBool(callOnCheck);
|
||||
this.callOnInit = normalizeBool(callOnInit);
|
||||
this.changeDetection = changeDetection;
|
||||
}
|
||||
|
||||
isDefaultChangeDetection(): boolean {
|
||||
return isDefaultChangeDetectionStrategy(this.changeDetection);
|
||||
}
|
||||
}
|
@ -0,0 +1,417 @@
|
||||
import {
|
||||
isPresent,
|
||||
isBlank,
|
||||
BaseException,
|
||||
FunctionWrapper,
|
||||
StringWrapper
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {AbstractChangeDetector} from './abstract_change_detector';
|
||||
import {EventBinding} from './event_binding';
|
||||
import {BindingRecord, BindingTarget} from './binding_record';
|
||||
import {DirectiveRecord, DirectiveIndex} from './directive_record';
|
||||
import {Locals} from './parser/locals';
|
||||
import {ChangeDetectorGenConfig} from './interfaces';
|
||||
import {ChangeDetectionUtil, SimpleChange} from './change_detection_util';
|
||||
import {ON_PUSH_OBSERVE} from './constants';
|
||||
import {ProtoRecord, RecordType} from './proto_record';
|
||||
|
||||
export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
||||
values: List<any>;
|
||||
changes: List<any>;
|
||||
localPipes: List<any>;
|
||||
prevContexts: List<any>;
|
||||
directives: any = null;
|
||||
|
||||
constructor(id: string, dispatcher: any, numberOfPropertyProtoRecords: number,
|
||||
propertyBindingTargets: BindingTarget[], directiveIndices: DirectiveIndex[],
|
||||
strategy: string, private records: ProtoRecord[],
|
||||
private eventBindings: EventBinding[], private directiveRecords: DirectiveRecord[],
|
||||
private genConfig: ChangeDetectorGenConfig) {
|
||||
super(id, dispatcher, numberOfPropertyProtoRecords, propertyBindingTargets, directiveIndices,
|
||||
strategy);
|
||||
var len = records.length + 1;
|
||||
this.values = ListWrapper.createFixedSize(len);
|
||||
this.localPipes = ListWrapper.createFixedSize(len);
|
||||
this.prevContexts = ListWrapper.createFixedSize(len);
|
||||
this.changes = ListWrapper.createFixedSize(len);
|
||||
|
||||
this.dehydrateDirectives(false);
|
||||
}
|
||||
|
||||
handleEventInternal(eventName: string, elIndex: number, locals: Locals): boolean {
|
||||
var preventDefault = false;
|
||||
|
||||
this._matchingEventBindings(eventName, elIndex)
|
||||
.forEach(rec => {
|
||||
var res = this._processEventBinding(rec, locals);
|
||||
if (res === false) {
|
||||
preventDefault = true;
|
||||
}
|
||||
});
|
||||
|
||||
return preventDefault;
|
||||
}
|
||||
|
||||
_processEventBinding(eb: EventBinding, locals: Locals): any {
|
||||
var values = ListWrapper.createFixedSize(eb.records.length);
|
||||
values[0] = this.values[0];
|
||||
|
||||
for (var i = 0; i < eb.records.length; ++i) {
|
||||
var proto = eb.records[i];
|
||||
var res = this._calculateCurrValue(proto, values, locals);
|
||||
if (proto.lastInBinding) {
|
||||
this._markPathAsCheckOnce(proto);
|
||||
return res;
|
||||
} else {
|
||||
this._writeSelf(proto, res, values);
|
||||
}
|
||||
}
|
||||
|
||||
throw new BaseException("Cannot be reached");
|
||||
}
|
||||
|
||||
_markPathAsCheckOnce(proto: ProtoRecord): void {
|
||||
if (!proto.bindingRecord.isDefaultChangeDetection()) {
|
||||
var dir = proto.bindingRecord.directiveRecord;
|
||||
this._getDetectorFor(dir.directiveIndex).markPathToRootAsCheckOnce();
|
||||
}
|
||||
}
|
||||
|
||||
_matchingEventBindings(eventName: string, elIndex: number): EventBinding[] {
|
||||
return ListWrapper.filter(this.eventBindings,
|
||||
eb => eb.eventName == eventName && eb.elIndex === elIndex);
|
||||
}
|
||||
|
||||
hydrateDirectives(directives: any): void {
|
||||
this.values[0] = this.context;
|
||||
this.directives = directives;
|
||||
|
||||
if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) {
|
||||
for (var i = 0; i < this.directiveIndices.length; ++i) {
|
||||
var index = this.directiveIndices[i];
|
||||
super.observeDirective(directives.getDirectiveFor(index), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dehydrateDirectives(destroyPipes: boolean) {
|
||||
if (destroyPipes) {
|
||||
this._destroyPipes();
|
||||
}
|
||||
this.values[0] = null;
|
||||
this.directives = null;
|
||||
ListWrapper.fill(this.values, ChangeDetectionUtil.uninitialized, 1);
|
||||
ListWrapper.fill(this.changes, false);
|
||||
ListWrapper.fill(this.localPipes, null);
|
||||
ListWrapper.fill(this.prevContexts, ChangeDetectionUtil.uninitialized);
|
||||
}
|
||||
|
||||
_destroyPipes() {
|
||||
for (var i = 0; i < this.localPipes.length; ++i) {
|
||||
if (isPresent(this.localPipes[i])) {
|
||||
ChangeDetectionUtil.callPipeOnDestroy(this.localPipes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkNoChanges(): void { this.runDetectChanges(true); }
|
||||
|
||||
detectChangesInRecordsInternal(throwOnChange: boolean) {
|
||||
var protos = this.records;
|
||||
|
||||
var changes = null;
|
||||
var isChanged = false;
|
||||
for (var i = 0; i < protos.length; ++i) {
|
||||
var proto: ProtoRecord = protos[i];
|
||||
var bindingRecord = proto.bindingRecord;
|
||||
var directiveRecord = bindingRecord.directiveRecord;
|
||||
|
||||
if (this._firstInBinding(proto)) {
|
||||
this.propertyBindingIndex = proto.propertyBindingIndex;
|
||||
}
|
||||
|
||||
if (proto.isLifeCycleRecord()) {
|
||||
if (proto.name === "onCheck" && !throwOnChange) {
|
||||
this._getDirectiveFor(directiveRecord.directiveIndex).onCheck();
|
||||
} else if (proto.name === "onInit" && !throwOnChange && !this.alreadyChecked) {
|
||||
this._getDirectiveFor(directiveRecord.directiveIndex).onInit();
|
||||
} else if (proto.name === "onChange" && isPresent(changes) && !throwOnChange) {
|
||||
this._getDirectiveFor(directiveRecord.directiveIndex).onChange(changes);
|
||||
}
|
||||
|
||||
} else {
|
||||
var change = this._check(proto, throwOnChange, this.values, this.locals);
|
||||
if (isPresent(change)) {
|
||||
this._updateDirectiveOrElement(change, bindingRecord);
|
||||
isChanged = true;
|
||||
changes = this._addChange(bindingRecord, change, changes);
|
||||
}
|
||||
}
|
||||
|
||||
if (proto.lastInDirective) {
|
||||
changes = null;
|
||||
if (isChanged && !bindingRecord.isDefaultChangeDetection()) {
|
||||
this._getDetectorFor(directiveRecord.directiveIndex).markAsCheckOnce();
|
||||
}
|
||||
|
||||
isChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.alreadyChecked = true;
|
||||
}
|
||||
|
||||
_firstInBinding(r: ProtoRecord): boolean {
|
||||
var prev = ChangeDetectionUtil.protoByIndex(this.records, r.selfIndex - 1);
|
||||
return isBlank(prev) || prev.bindingRecord !== r.bindingRecord;
|
||||
}
|
||||
|
||||
callOnAllChangesDone() {
|
||||
super.callOnAllChangesDone();
|
||||
var dirs = this.directiveRecords;
|
||||
for (var i = dirs.length - 1; i >= 0; --i) {
|
||||
var dir = dirs[i];
|
||||
if (dir.callOnAllChangesDone) {
|
||||
this._getDirectiveFor(dir.directiveIndex).onAllChangesDone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_updateDirectiveOrElement(change, bindingRecord) {
|
||||
if (isBlank(bindingRecord.directiveRecord)) {
|
||||
super.notifyDispatcher(change.currentValue);
|
||||
} else {
|
||||
var directiveIndex = bindingRecord.directiveRecord.directiveIndex;
|
||||
bindingRecord.setter(this._getDirectiveFor(directiveIndex), change.currentValue);
|
||||
}
|
||||
|
||||
if (this.genConfig.logBindingUpdate) {
|
||||
super.logBindingUpdate(change.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
_addChange(bindingRecord: BindingRecord, change, changes) {
|
||||
if (bindingRecord.callOnChange()) {
|
||||
return super.addChange(changes, change.previousValue, change.currentValue);
|
||||
} else {
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
||||
_getDirectiveFor(directiveIndex) { return this.directives.getDirectiveFor(directiveIndex); }
|
||||
|
||||
_getDetectorFor(directiveIndex) { return this.directives.getDetectorFor(directiveIndex); }
|
||||
|
||||
_check(proto: ProtoRecord, throwOnChange: boolean, values: any[], locals: Locals): SimpleChange {
|
||||
if (proto.isPipeRecord()) {
|
||||
return this._pipeCheck(proto, throwOnChange, values);
|
||||
} else {
|
||||
return this._referenceCheck(proto, throwOnChange, values, locals);
|
||||
}
|
||||
}
|
||||
|
||||
_referenceCheck(proto: ProtoRecord, throwOnChange: boolean, values: any[], locals: Locals) {
|
||||
if (this._pureFuncAndArgsDidNotChange(proto)) {
|
||||
this._setChanged(proto, false);
|
||||
return null;
|
||||
}
|
||||
|
||||
var currValue = this._calculateCurrValue(proto, values, locals);
|
||||
if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) {
|
||||
super.observeValue(currValue, proto.selfIndex);
|
||||
}
|
||||
|
||||
if (proto.shouldBeChecked()) {
|
||||
var prevValue = this._readSelf(proto, values);
|
||||
if (!isSame(prevValue, currValue)) {
|
||||
if (proto.lastInBinding) {
|
||||
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
|
||||
if (throwOnChange) this.throwOnChangeError(prevValue, currValue);
|
||||
|
||||
this._writeSelf(proto, currValue, values);
|
||||
this._setChanged(proto, true);
|
||||
return change;
|
||||
} else {
|
||||
this._writeSelf(proto, currValue, values);
|
||||
this._setChanged(proto, true);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
this._setChanged(proto, false);
|
||||
return null;
|
||||
}
|
||||
|
||||
} else {
|
||||
this._writeSelf(proto, currValue, values);
|
||||
this._setChanged(proto, true);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
_calculateCurrValue(proto: ProtoRecord, values: any[], locals: Locals) {
|
||||
switch (proto.mode) {
|
||||
case RecordType.SELF:
|
||||
return this._readContext(proto, values);
|
||||
|
||||
case RecordType.CONST:
|
||||
return proto.funcOrValue;
|
||||
|
||||
case RecordType.PROPERTY_READ:
|
||||
var context = this._readContext(proto, values);
|
||||
return proto.funcOrValue(context);
|
||||
|
||||
case RecordType.SAFE_PROPERTY:
|
||||
var context = this._readContext(proto, values);
|
||||
return isBlank(context) ? null : proto.funcOrValue(context);
|
||||
|
||||
case RecordType.PROPERTY_WRITE:
|
||||
var context = this._readContext(proto, values);
|
||||
var value = this._readArgs(proto, values)[0];
|
||||
proto.funcOrValue(context, value);
|
||||
return value;
|
||||
|
||||
case RecordType.KEYED_WRITE:
|
||||
var context = this._readContext(proto, values);
|
||||
var key = this._readArgs(proto, values)[0];
|
||||
var value = this._readArgs(proto, values)[1];
|
||||
context[key] = value;
|
||||
return value;
|
||||
|
||||
case RecordType.LOCAL:
|
||||
return locals.get(proto.name);
|
||||
|
||||
case RecordType.INVOKE_METHOD:
|
||||
var context = this._readContext(proto, values);
|
||||
var args = this._readArgs(proto, values);
|
||||
return proto.funcOrValue(context, args);
|
||||
|
||||
case RecordType.SAFE_INVOKE_METHOD:
|
||||
var context = this._readContext(proto, values);
|
||||
if (isBlank(context)) {
|
||||
return null;
|
||||
}
|
||||
var args = this._readArgs(proto, values);
|
||||
return proto.funcOrValue(context, args);
|
||||
|
||||
case RecordType.KEYED_READ:
|
||||
var arg = this._readArgs(proto, values)[0];
|
||||
return this._readContext(proto, values)[arg];
|
||||
|
||||
case RecordType.CHAIN:
|
||||
var args = this._readArgs(proto, values);
|
||||
return args[args.length - 1];
|
||||
|
||||
case RecordType.INVOKE_CLOSURE:
|
||||
return FunctionWrapper.apply(this._readContext(proto, values),
|
||||
this._readArgs(proto, values));
|
||||
|
||||
case RecordType.INTERPOLATE:
|
||||
case RecordType.PRIMITIVE_OP:
|
||||
case RecordType.COLLECTION_LITERAL:
|
||||
return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto, values));
|
||||
|
||||
default:
|
||||
throw new BaseException(`Unknown operation ${proto.mode}`);
|
||||
}
|
||||
}
|
||||
|
||||
_pipeCheck(proto: ProtoRecord, throwOnChange: boolean, values: any[]) {
|
||||
var context = this._readContext(proto, values);
|
||||
var args = this._readArgs(proto, values);
|
||||
|
||||
var pipe = this._pipeFor(proto, context);
|
||||
var currValue = pipe.transform(context, args);
|
||||
|
||||
if (proto.shouldBeChecked()) {
|
||||
var prevValue = this._readSelf(proto, values);
|
||||
if (!isSame(prevValue, currValue)) {
|
||||
currValue = ChangeDetectionUtil.unwrapValue(currValue);
|
||||
|
||||
if (proto.lastInBinding) {
|
||||
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
|
||||
if (throwOnChange) this.throwOnChangeError(prevValue, currValue);
|
||||
|
||||
this._writeSelf(proto, currValue, values);
|
||||
this._setChanged(proto, true);
|
||||
|
||||
return change;
|
||||
|
||||
} else {
|
||||
this._writeSelf(proto, currValue, values);
|
||||
this._setChanged(proto, true);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
this._setChanged(proto, false);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
this._writeSelf(proto, currValue, values);
|
||||
this._setChanged(proto, true);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
_pipeFor(proto: ProtoRecord, context) {
|
||||
var storedPipe = this._readPipe(proto);
|
||||
if (isPresent(storedPipe)) return storedPipe;
|
||||
|
||||
var pipe = this.pipes.get(proto.name);
|
||||
this._writePipe(proto, pipe);
|
||||
return pipe;
|
||||
}
|
||||
|
||||
_readContext(proto: ProtoRecord, values: any[]) {
|
||||
if (proto.contextIndex == -1) {
|
||||
return this._getDirectiveFor(proto.directiveIndex);
|
||||
} else {
|
||||
return values[proto.contextIndex];
|
||||
}
|
||||
|
||||
return values[proto.contextIndex];
|
||||
}
|
||||
|
||||
_readSelf(proto: ProtoRecord, values: any[]) { return values[proto.selfIndex]; }
|
||||
|
||||
_writeSelf(proto: ProtoRecord, value, values: any[]) { values[proto.selfIndex] = value; }
|
||||
|
||||
_readPipe(proto: ProtoRecord) { return this.localPipes[proto.selfIndex]; }
|
||||
|
||||
_writePipe(proto: ProtoRecord, value) { this.localPipes[proto.selfIndex] = value; }
|
||||
|
||||
_setChanged(proto: ProtoRecord, value: boolean) {
|
||||
if (proto.argumentToPureFunction) this.changes[proto.selfIndex] = value;
|
||||
}
|
||||
|
||||
_pureFuncAndArgsDidNotChange(proto: ProtoRecord): boolean {
|
||||
return proto.isPureFunction() && !this._argsChanged(proto);
|
||||
}
|
||||
|
||||
_argsChanged(proto: ProtoRecord): boolean {
|
||||
var args = proto.args;
|
||||
for (var i = 0; i < args.length; ++i) {
|
||||
if (this.changes[args[i]]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_readArgs(proto: ProtoRecord, values: any[]) {
|
||||
var res = ListWrapper.createFixedSize(proto.args.length);
|
||||
var args = proto.args;
|
||||
for (var i = 0; i < args.length; ++i) {
|
||||
res[i] = values[args[i]];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
function isSame(a, b): boolean {
|
||||
if (a === b) return true;
|
||||
if (a instanceof String && b instanceof String && a == b) return true;
|
||||
if ((a !== a) && (b !== b)) return true;
|
||||
return false;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import {DirectiveIndex} from './directive_record';
|
||||
import {ProtoRecord} from './proto_record';
|
||||
|
||||
export class EventBinding {
|
||||
constructor(public eventName: string, public elIndex: number, public dirIndex: DirectiveIndex,
|
||||
public records: ProtoRecord[]) {}
|
||||
}
|
43
modules/angular2/src/core/change_detection/exceptions.ts
Normal file
43
modules/angular2/src/core/change_detection/exceptions.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import {BaseException} from "angular2/src/facade/lang";
|
||||
|
||||
/**
|
||||
* An error thrown if application changes model breaking the top-down data flow.
|
||||
*
|
||||
* Angular expects that the data flows from top (root) component to child (leaf) components.
|
||||
* This is known as directed acyclic graph. This allows Angular to only execute change detection
|
||||
* once and prevents loops in change detection data flow.
|
||||
*
|
||||
* This exception is only thrown in dev mode.
|
||||
*/
|
||||
export class ExpressionChangedAfterItHasBeenCheckedException extends BaseException {
|
||||
constructor(exp: string, oldValue: any, currValue: any, context: any) {
|
||||
super(`Expression '${exp}' has changed after it was checked. ` +
|
||||
`Previous value: '${oldValue}'. Current value: '${currValue}'`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when an expression evaluation raises an exception.
|
||||
*
|
||||
* This error wraps the original exception, this is done to attach expression location information.
|
||||
*/
|
||||
export class ChangeDetectionError extends BaseException {
|
||||
/**
|
||||
* Location of the expression.
|
||||
*/
|
||||
location: string;
|
||||
|
||||
constructor(exp: string, originalException: any, originalStack: any, context: any) {
|
||||
super(`${originalException} in [${exp}]`, originalException, originalStack, context);
|
||||
this.location = exp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when change detector executes on dehydrated view.
|
||||
*
|
||||
* This is angular internal error.
|
||||
*/
|
||||
export class DehydratedException extends BaseException {
|
||||
constructor() { super('Attempt to detect changes on a dehydrated detector.'); }
|
||||
}
|
87
modules/angular2/src/core/change_detection/interfaces.ts
Normal file
87
modules/angular2/src/core/change_detection/interfaces.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import {List} from 'angular2/src/facade/collection';
|
||||
import {CONST} from 'angular2/src/facade/lang';
|
||||
import {Locals} from './parser/locals';
|
||||
import {BindingTarget, BindingRecord} from './binding_record';
|
||||
import {DirectiveIndex, DirectiveRecord} from './directive_record';
|
||||
import {ChangeDetectorRef} from './change_detector_ref';
|
||||
|
||||
/**
|
||||
* Interface used by Angular to control the change detection strategy for an application.
|
||||
*
|
||||
* Angular implements the following change detection strategies by default:
|
||||
*
|
||||
* - {@link DynamicChangeDetection}: slower, but does not require `eval()`.
|
||||
* - {@link JitChangeDetection}: faster, but requires `eval()`.
|
||||
*
|
||||
* In JavaScript, you should always use `JitChangeDetection`, unless you are in an environment that
|
||||
*has
|
||||
* [CSP](https://developer.mozilla.org/en-US/docs/Web/Security/CSP), such as a Chrome Extension.
|
||||
*
|
||||
* In Dart, use `DynamicChangeDetection` during development. The Angular transformer generates an
|
||||
*analog to the
|
||||
* `JitChangeDetection` strategy at compile time.
|
||||
*
|
||||
*
|
||||
* See: {@link DynamicChangeDetection}, {@link JitChangeDetection},
|
||||
* {@link PreGeneratedChangeDetection}
|
||||
*
|
||||
* # Example
|
||||
* ```javascript
|
||||
* bootstrap(MyApp, [bind(ChangeDetection).toValue(new DynamicChangeDetection())]);
|
||||
* ```
|
||||
*/
|
||||
@CONST()
|
||||
export class ChangeDetection {
|
||||
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
|
||||
return null;
|
||||
}
|
||||
|
||||
get generateDetectors(): boolean { return null; }
|
||||
|
||||
get genConfig(): ChangeDetectorGenConfig { return null; }
|
||||
}
|
||||
|
||||
export class DebugContext {
|
||||
constructor(public element: any, public componentElement: any, public directive: any,
|
||||
public context: any, public locals: any, public injector: any) {}
|
||||
}
|
||||
|
||||
export interface ChangeDispatcher {
|
||||
getDebugContext(elementIndex: number, directiveIndex: DirectiveIndex): DebugContext;
|
||||
notifyOnBinding(bindingTarget: BindingTarget, value: any): void;
|
||||
logBindingUpdate(bindingTarget: BindingTarget, value: any): void;
|
||||
notifyOnAllChangesDone(): void;
|
||||
}
|
||||
|
||||
export interface ChangeDetector {
|
||||
parent: ChangeDetector;
|
||||
mode: string;
|
||||
ref: ChangeDetectorRef;
|
||||
|
||||
addChild(cd: ChangeDetector): void;
|
||||
addShadowDomChild(cd: ChangeDetector): void;
|
||||
removeChild(cd: ChangeDetector): void;
|
||||
removeShadowDomChild(cd: ChangeDetector): void;
|
||||
remove(): void;
|
||||
hydrate(context: any, locals: Locals, directives: any, pipes: any): void;
|
||||
dehydrate(): void;
|
||||
markPathToRootAsCheckOnce(): void;
|
||||
|
||||
handleEvent(eventName: string, elIndex: number, locals: Locals);
|
||||
detectChanges(): void;
|
||||
checkNoChanges(): void;
|
||||
}
|
||||
|
||||
export interface ProtoChangeDetector { instantiate(dispatcher: ChangeDispatcher): ChangeDetector; }
|
||||
|
||||
export class ChangeDetectorGenConfig {
|
||||
constructor(public genCheckNoChanges: boolean, public genDebugInfo: boolean,
|
||||
public logBindingUpdate: boolean) {}
|
||||
}
|
||||
|
||||
export class ChangeDetectorDefinition {
|
||||
constructor(public id: string, public strategy: string, public variableNames: List<string>,
|
||||
public bindingRecords: BindingRecord[], public eventRecords: BindingRecord[],
|
||||
public directiveRecords: DirectiveRecord[],
|
||||
public genConfig: ChangeDetectorGenConfig) {}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
library change_detection.jit_proto_change_detector;
|
||||
|
||||
import 'interfaces.dart' show ChangeDetector, ProtoChangeDetector;
|
||||
|
||||
class JitProtoChangeDetector implements ProtoChangeDetector {
|
||||
JitProtoChangeDetector(definition) : super();
|
||||
|
||||
static bool isSupported() => false;
|
||||
|
||||
ChangeDetector instantiate(dispatcher) {
|
||||
throw "Jit Change Detection not supported in Dart";
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
|
||||
import {ProtoChangeDetector, ChangeDetector, ChangeDetectorDefinition} from './interfaces';
|
||||
import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
|
||||
|
||||
import {coalesce} from './coalesce';
|
||||
import {createPropertyRecords, createEventRecords} from './proto_change_detector';
|
||||
|
||||
export class JitProtoChangeDetector implements ProtoChangeDetector {
|
||||
_factory: Function;
|
||||
|
||||
constructor(private definition: ChangeDetectorDefinition) {
|
||||
this._factory = this._createFactory(definition);
|
||||
}
|
||||
|
||||
static isSupported(): boolean { return true; }
|
||||
|
||||
instantiate(dispatcher: any): ChangeDetector { return this._factory(dispatcher); }
|
||||
|
||||
_createFactory(definition: ChangeDetectorDefinition) {
|
||||
var propertyBindingRecords = createPropertyRecords(definition);
|
||||
var eventBindingRecords = createEventRecords(definition);
|
||||
var propertyBindingTargets = this.definition.bindingRecords.map(b => b.target);
|
||||
|
||||
return new ChangeDetectorJITGenerator(
|
||||
definition.id, definition.strategy, propertyBindingRecords, propertyBindingTargets,
|
||||
eventBindingRecords, this.definition.directiveRecords, this.definition.genConfig)
|
||||
.generate();
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
import 'package:observe/observe.dart';
|
||||
|
||||
bool isObservable(value) => value is Observable;
|
@ -0,0 +1,3 @@
|
||||
export function isObservable(value: any): boolean {
|
||||
return false;
|
||||
}
|
311
modules/angular2/src/core/change_detection/parser/ast.ts
Normal file
311
modules/angular2/src/core/change_detection/parser/ast.ts
Normal file
@ -0,0 +1,311 @@
|
||||
import {isBlank, isPresent, FunctionWrapper, BaseException} from "angular2/src/facade/lang";
|
||||
import {List, Map, ListWrapper, StringMapWrapper} from "angular2/src/facade/collection";
|
||||
|
||||
export class AST {
|
||||
visit(visitor: AstVisitor): any { return null; }
|
||||
toString(): string { return "AST"; }
|
||||
}
|
||||
|
||||
export class EmptyExpr extends AST {
|
||||
visit(visitor: AstVisitor) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
export class ImplicitReceiver extends AST {
|
||||
visit(visitor: AstVisitor): any { return visitor.visitImplicitReceiver(this); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiple expressions separated by a semicolon.
|
||||
*/
|
||||
export class Chain extends AST {
|
||||
constructor(public expressions: List<any>) { super(); }
|
||||
visit(visitor: AstVisitor): any { return visitor.visitChain(this); }
|
||||
}
|
||||
|
||||
export class Conditional extends AST {
|
||||
constructor(public condition: AST, public trueExp: AST, public falseExp: AST) { super(); }
|
||||
visit(visitor: AstVisitor): any { return visitor.visitConditional(this); }
|
||||
}
|
||||
|
||||
export class If extends AST {
|
||||
constructor(public condition: AST, public trueExp: AST, public falseExp?: AST) { super(); }
|
||||
visit(visitor: AstVisitor): any { return visitor.visitIf(this); }
|
||||
}
|
||||
|
||||
export class PropertyRead extends AST {
|
||||
constructor(public receiver: AST, public name: string, public getter: Function) { super(); }
|
||||
visit(visitor: AstVisitor): any { return visitor.visitPropertyRead(this); }
|
||||
}
|
||||
|
||||
export class PropertyWrite extends AST {
|
||||
constructor(public receiver: AST, public name: string, public setter: Function,
|
||||
public value: AST) {
|
||||
super();
|
||||
}
|
||||
visit(visitor: AstVisitor): any { return visitor.visitPropertyWrite(this); }
|
||||
}
|
||||
|
||||
export class SafePropertyRead extends AST {
|
||||
constructor(public receiver: AST, public name: string, public getter: Function) { super(); }
|
||||
visit(visitor: AstVisitor): any { return visitor.visitSafePropertyRead(this); }
|
||||
}
|
||||
|
||||
export class KeyedRead extends AST {
|
||||
constructor(public obj: AST, public key: AST) { super(); }
|
||||
visit(visitor: AstVisitor): any { return visitor.visitKeyedRead(this); }
|
||||
}
|
||||
|
||||
export class KeyedWrite extends AST {
|
||||
constructor(public obj: AST, public key: AST, public value: AST) { super(); }
|
||||
visit(visitor: AstVisitor): any { return visitor.visitKeyedWrite(this); }
|
||||
}
|
||||
|
||||
export class BindingPipe extends AST {
|
||||
constructor(public exp: AST, public name: string, public args: List<any>) { super(); }
|
||||
|
||||
visit(visitor: AstVisitor): any { return visitor.visitPipe(this); }
|
||||
}
|
||||
|
||||
export class LiteralPrimitive extends AST {
|
||||
constructor(public value) { super(); }
|
||||
visit(visitor: AstVisitor): any { return visitor.visitLiteralPrimitive(this); }
|
||||
}
|
||||
|
||||
export class LiteralArray extends AST {
|
||||
constructor(public expressions: List<any>) { super(); }
|
||||
visit(visitor: AstVisitor): any { return visitor.visitLiteralArray(this); }
|
||||
}
|
||||
|
||||
export class LiteralMap extends AST {
|
||||
constructor(public keys: List<any>, public values: List<any>) { super(); }
|
||||
visit(visitor: AstVisitor): any { return visitor.visitLiteralMap(this); }
|
||||
}
|
||||
|
||||
export class Interpolation extends AST {
|
||||
constructor(public strings: List<any>, public expressions: List<any>) { super(); }
|
||||
visit(visitor: AstVisitor) { visitor.visitInterpolation(this); }
|
||||
}
|
||||
|
||||
export class Binary extends AST {
|
||||
constructor(public operation: string, public left: AST, public right: AST) { super(); }
|
||||
visit(visitor: AstVisitor): any { return visitor.visitBinary(this); }
|
||||
}
|
||||
|
||||
export class PrefixNot extends AST {
|
||||
constructor(public expression: AST) { super(); }
|
||||
visit(visitor: AstVisitor): any { return visitor.visitPrefixNot(this); }
|
||||
}
|
||||
|
||||
export class MethodCall extends AST {
|
||||
constructor(public receiver: AST, public name: string, public fn: Function,
|
||||
public args: List<any>) {
|
||||
super();
|
||||
}
|
||||
visit(visitor: AstVisitor): any { return visitor.visitMethodCall(this); }
|
||||
}
|
||||
|
||||
export class SafeMethodCall extends AST {
|
||||
constructor(public receiver: AST, public name: string, public fn: Function,
|
||||
public args: List<any>) {
|
||||
super();
|
||||
}
|
||||
visit(visitor: AstVisitor): any { return visitor.visitSafeMethodCall(this); }
|
||||
}
|
||||
|
||||
export class FunctionCall extends AST {
|
||||
constructor(public target: AST, public args: List<any>) { super(); }
|
||||
visit(visitor: AstVisitor): any { return visitor.visitFunctionCall(this); }
|
||||
}
|
||||
|
||||
export class ASTWithSource extends AST {
|
||||
constructor(public ast: AST, public source: string, public location: string) { super(); }
|
||||
visit(visitor: AstVisitor): any { return this.ast.visit(visitor); }
|
||||
toString(): string { return `${this.source} in ${this.location}`; }
|
||||
}
|
||||
|
||||
export class TemplateBinding {
|
||||
constructor(public key: string, public keyIsVar: boolean, public name: string,
|
||||
public expression: ASTWithSource) {}
|
||||
}
|
||||
|
||||
export interface AstVisitor {
|
||||
visitBinary(ast: Binary): any;
|
||||
visitChain(ast: Chain): any;
|
||||
visitConditional(ast: Conditional): any;
|
||||
visitFunctionCall(ast: FunctionCall): any;
|
||||
visitIf(ast: If): any;
|
||||
visitImplicitReceiver(ast: ImplicitReceiver): any;
|
||||
visitInterpolation(ast: Interpolation): any;
|
||||
visitKeyedRead(ast: KeyedRead): any;
|
||||
visitKeyedWrite(ast: KeyedWrite): any;
|
||||
visitLiteralArray(ast: LiteralArray): any;
|
||||
visitLiteralMap(ast: LiteralMap): any;
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive): any;
|
||||
visitMethodCall(ast: MethodCall): any;
|
||||
visitPipe(ast: BindingPipe): any;
|
||||
visitPrefixNot(ast: PrefixNot): any;
|
||||
visitPropertyRead(ast: PropertyRead): any;
|
||||
visitPropertyWrite(ast: PropertyWrite): any;
|
||||
visitSafeMethodCall(ast: SafeMethodCall): any;
|
||||
visitSafePropertyRead(ast: SafePropertyRead): any;
|
||||
}
|
||||
|
||||
export class RecursiveAstVisitor implements AstVisitor {
|
||||
visitBinary(ast: Binary): any {
|
||||
ast.left.visit(this);
|
||||
ast.right.visit(this);
|
||||
return null;
|
||||
}
|
||||
visitChain(ast: Chain): any { return this.visitAll(ast.expressions); }
|
||||
visitConditional(ast: Conditional): any {
|
||||
ast.condition.visit(this);
|
||||
ast.trueExp.visit(this);
|
||||
ast.falseExp.visit(this);
|
||||
return null;
|
||||
}
|
||||
visitIf(ast: If): any {
|
||||
ast.condition.visit(this);
|
||||
ast.trueExp.visit(this);
|
||||
ast.falseExp.visit(this);
|
||||
return null;
|
||||
}
|
||||
visitPipe(ast: BindingPipe): any {
|
||||
ast.exp.visit(this);
|
||||
this.visitAll(ast.args);
|
||||
return null;
|
||||
}
|
||||
visitFunctionCall(ast: FunctionCall): any {
|
||||
ast.target.visit(this);
|
||||
this.visitAll(ast.args);
|
||||
return null;
|
||||
}
|
||||
visitImplicitReceiver(ast: ImplicitReceiver): any { return null; }
|
||||
visitInterpolation(ast: Interpolation): any { return this.visitAll(ast.expressions); }
|
||||
visitKeyedRead(ast: KeyedRead): any {
|
||||
ast.obj.visit(this);
|
||||
ast.key.visit(this);
|
||||
return null;
|
||||
}
|
||||
visitKeyedWrite(ast: KeyedWrite): any {
|
||||
ast.obj.visit(this);
|
||||
ast.key.visit(this);
|
||||
ast.value.visit(this);
|
||||
return null;
|
||||
}
|
||||
visitLiteralArray(ast: LiteralArray): any { return this.visitAll(ast.expressions); }
|
||||
visitLiteralMap(ast: LiteralMap): any { return this.visitAll(ast.values); }
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive): any { return null; }
|
||||
visitMethodCall(ast: MethodCall): any {
|
||||
ast.receiver.visit(this);
|
||||
return this.visitAll(ast.args);
|
||||
}
|
||||
visitPrefixNot(ast: PrefixNot): any {
|
||||
ast.expression.visit(this);
|
||||
return null;
|
||||
}
|
||||
visitPropertyRead(ast: PropertyRead): any {
|
||||
ast.receiver.visit(this);
|
||||
return null;
|
||||
}
|
||||
visitPropertyWrite(ast: PropertyWrite): any {
|
||||
ast.receiver.visit(this);
|
||||
ast.value.visit(this);
|
||||
return null;
|
||||
}
|
||||
visitSafePropertyRead(ast: SafePropertyRead): any {
|
||||
ast.receiver.visit(this);
|
||||
return null;
|
||||
}
|
||||
visitSafeMethodCall(ast: SafeMethodCall): any {
|
||||
ast.receiver.visit(this);
|
||||
return this.visitAll(ast.args);
|
||||
}
|
||||
visitAll(asts: List<AST>): any {
|
||||
ListWrapper.forEach(asts, (ast) => { ast.visit(this); });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class AstTransformer implements AstVisitor {
|
||||
visitImplicitReceiver(ast: ImplicitReceiver): ImplicitReceiver { return ast; }
|
||||
|
||||
visitInterpolation(ast: Interpolation): Interpolation {
|
||||
return new Interpolation(ast.strings, this.visitAll(ast.expressions));
|
||||
}
|
||||
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive): LiteralPrimitive {
|
||||
return new LiteralPrimitive(ast.value);
|
||||
}
|
||||
|
||||
visitPropertyRead(ast: PropertyRead): PropertyRead {
|
||||
return new PropertyRead(ast.receiver.visit(this), ast.name, ast.getter);
|
||||
}
|
||||
|
||||
visitPropertyWrite(ast: PropertyWrite): PropertyWrite {
|
||||
return new PropertyWrite(ast.receiver.visit(this), ast.name, ast.setter, ast.value);
|
||||
}
|
||||
|
||||
visitSafePropertyRead(ast: SafePropertyRead): SafePropertyRead {
|
||||
return new SafePropertyRead(ast.receiver.visit(this), ast.name, ast.getter);
|
||||
}
|
||||
|
||||
visitMethodCall(ast: MethodCall): MethodCall {
|
||||
return new MethodCall(ast.receiver.visit(this), ast.name, ast.fn, this.visitAll(ast.args));
|
||||
}
|
||||
|
||||
visitSafeMethodCall(ast: SafeMethodCall): SafeMethodCall {
|
||||
return new SafeMethodCall(ast.receiver.visit(this), ast.name, ast.fn, this.visitAll(ast.args));
|
||||
}
|
||||
|
||||
visitFunctionCall(ast: FunctionCall): FunctionCall {
|
||||
return new FunctionCall(ast.target.visit(this), this.visitAll(ast.args));
|
||||
}
|
||||
|
||||
visitLiteralArray(ast: LiteralArray): LiteralArray {
|
||||
return new LiteralArray(this.visitAll(ast.expressions));
|
||||
}
|
||||
|
||||
visitLiteralMap(ast: LiteralMap): LiteralMap {
|
||||
return new LiteralMap(ast.keys, this.visitAll(ast.values));
|
||||
}
|
||||
|
||||
visitBinary(ast: Binary): Binary {
|
||||
return new Binary(ast.operation, ast.left.visit(this), ast.right.visit(this));
|
||||
}
|
||||
|
||||
visitPrefixNot(ast: PrefixNot): PrefixNot { return new PrefixNot(ast.expression.visit(this)); }
|
||||
|
||||
visitConditional(ast: Conditional): Conditional {
|
||||
return new Conditional(ast.condition.visit(this), ast.trueExp.visit(this),
|
||||
ast.falseExp.visit(this));
|
||||
}
|
||||
|
||||
visitPipe(ast: BindingPipe): BindingPipe {
|
||||
return new BindingPipe(ast.exp.visit(this), ast.name, this.visitAll(ast.args));
|
||||
}
|
||||
|
||||
visitKeyedRead(ast: KeyedRead): KeyedRead {
|
||||
return new KeyedRead(ast.obj.visit(this), ast.key.visit(this));
|
||||
}
|
||||
|
||||
visitKeyedWrite(ast: KeyedWrite): KeyedWrite {
|
||||
return new KeyedWrite(ast.obj.visit(this), ast.key.visit(this), ast.value.visit(this));
|
||||
}
|
||||
|
||||
visitAll(asts: List<any>): List<any> {
|
||||
var res = ListWrapper.createFixedSize(asts.length);
|
||||
for (var i = 0; i < asts.length; ++i) {
|
||||
res[i] = asts[i].visit(this);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
visitChain(ast: Chain): Chain { return new Chain(this.visitAll(ast.expressions)); }
|
||||
|
||||
visitIf(ast: If): If {
|
||||
let falseExp = isPresent(ast.falseExp) ? ast.falseExp.visit(this) : null;
|
||||
return new If(ast.condition.visit(this), ast.trueExp.visit(this), falseExp);
|
||||
}
|
||||
}
|
468
modules/angular2/src/core/change_detection/parser/lexer.ts
Normal file
468
modules/angular2/src/core/change_detection/parser/lexer.ts
Normal file
@ -0,0 +1,468 @@
|
||||
import {Injectable} from 'angular2/src/di/decorators';
|
||||
import {List, ListWrapper, SetWrapper} from "angular2/src/facade/collection";
|
||||
import {
|
||||
NumberWrapper,
|
||||
StringJoiner,
|
||||
StringWrapper,
|
||||
BaseException,
|
||||
isPresent
|
||||
} from "angular2/src/facade/lang";
|
||||
|
||||
export enum TokenType {
|
||||
CHARACTER,
|
||||
IDENTIFIER,
|
||||
KEYWORD,
|
||||
STRING,
|
||||
OPERATOR,
|
||||
NUMBER
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class Lexer {
|
||||
tokenize(text: string): List<any> {
|
||||
var scanner = new _Scanner(text);
|
||||
var tokens = [];
|
||||
var token = scanner.scanToken();
|
||||
while (token != null) {
|
||||
tokens.push(token);
|
||||
token = scanner.scanToken();
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
|
||||
export class Token {
|
||||
constructor(public index: number, public type: TokenType, public numValue: number,
|
||||
public strValue: string) {}
|
||||
|
||||
isCharacter(code: number): boolean {
|
||||
return (this.type == TokenType.CHARACTER && this.numValue == code);
|
||||
}
|
||||
|
||||
isNumber(): boolean { return (this.type == TokenType.NUMBER); }
|
||||
|
||||
isString(): boolean { return (this.type == TokenType.STRING); }
|
||||
|
||||
isOperator(operater: string): boolean {
|
||||
return (this.type == TokenType.OPERATOR && this.strValue == operater);
|
||||
}
|
||||
|
||||
isIdentifier(): boolean { return (this.type == TokenType.IDENTIFIER); }
|
||||
|
||||
isKeyword(): boolean { return (this.type == TokenType.KEYWORD); }
|
||||
|
||||
isKeywordVar(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "var"); }
|
||||
|
||||
isKeywordNull(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "null"); }
|
||||
|
||||
isKeywordUndefined(): boolean {
|
||||
return (this.type == TokenType.KEYWORD && this.strValue == "undefined");
|
||||
}
|
||||
|
||||
isKeywordTrue(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "true"); }
|
||||
|
||||
isKeywordIf(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "if"); }
|
||||
|
||||
isKeywordElse(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "else"); }
|
||||
|
||||
isKeywordFalse(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "false"); }
|
||||
|
||||
toNumber(): number {
|
||||
// -1 instead of NULL ok?
|
||||
return (this.type == TokenType.NUMBER) ? this.numValue : -1;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
switch (this.type) {
|
||||
case TokenType.CHARACTER:
|
||||
case TokenType.STRING:
|
||||
case TokenType.IDENTIFIER:
|
||||
case TokenType.KEYWORD:
|
||||
return this.strValue;
|
||||
case TokenType.NUMBER:
|
||||
return this.numValue.toString();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function newCharacterToken(index: number, code: number): Token {
|
||||
return new Token(index, TokenType.CHARACTER, code, StringWrapper.fromCharCode(code));
|
||||
}
|
||||
|
||||
function newIdentifierToken(index: number, text: string): Token {
|
||||
return new Token(index, TokenType.IDENTIFIER, 0, text);
|
||||
}
|
||||
|
||||
function newKeywordToken(index: number, text: string): Token {
|
||||
return new Token(index, TokenType.KEYWORD, 0, text);
|
||||
}
|
||||
|
||||
function newOperatorToken(index: number, text: string): Token {
|
||||
return new Token(index, TokenType.OPERATOR, 0, text);
|
||||
}
|
||||
|
||||
function newStringToken(index: number, text: string): Token {
|
||||
return new Token(index, TokenType.STRING, 0, text);
|
||||
}
|
||||
|
||||
function newNumberToken(index: number, n: number): Token {
|
||||
return new Token(index, TokenType.NUMBER, n, "");
|
||||
}
|
||||
|
||||
|
||||
export var EOF: Token = new Token(-1, TokenType.CHARACTER, 0, "");
|
||||
|
||||
export const $EOF = 0;
|
||||
export const $TAB = 9;
|
||||
export const $LF = 10;
|
||||
export const $VTAB = 11;
|
||||
export const $FF = 12;
|
||||
export const $CR = 13;
|
||||
export const $SPACE = 32;
|
||||
export const $BANG = 33;
|
||||
export const $DQ = 34;
|
||||
export const $HASH = 35;
|
||||
export const $$ = 36;
|
||||
export const $PERCENT = 37;
|
||||
export const $AMPERSAND = 38;
|
||||
export const $SQ = 39;
|
||||
export const $LPAREN = 40;
|
||||
export const $RPAREN = 41;
|
||||
export const $STAR = 42;
|
||||
export const $PLUS = 43;
|
||||
export const $COMMA = 44;
|
||||
export const $MINUS = 45;
|
||||
export const $PERIOD = 46;
|
||||
export const $SLASH = 47;
|
||||
export const $COLON = 58;
|
||||
export const $SEMICOLON = 59;
|
||||
export const $LT = 60;
|
||||
export const $EQ = 61;
|
||||
export const $GT = 62;
|
||||
export const $QUESTION = 63;
|
||||
|
||||
const $0 = 48;
|
||||
const $9 = 57;
|
||||
|
||||
const $A = 65, $E = 69, $Z = 90;
|
||||
|
||||
export const $LBRACKET = 91;
|
||||
export const $BACKSLASH = 92;
|
||||
export const $RBRACKET = 93;
|
||||
const $CARET = 94;
|
||||
const $_ = 95;
|
||||
|
||||
const $a = 97, $e = 101, $f = 102, $n = 110, $r = 114, $t = 116, $u = 117, $v = 118, $z = 122;
|
||||
|
||||
export const $LBRACE = 123;
|
||||
export const $BAR = 124;
|
||||
export const $RBRACE = 125;
|
||||
const $NBSP = 160;
|
||||
|
||||
|
||||
export class ScannerError extends BaseException {
|
||||
constructor(public message) { super(); }
|
||||
|
||||
toString(): string { return this.message; }
|
||||
}
|
||||
|
||||
class _Scanner {
|
||||
length: number;
|
||||
peek: number = 0;
|
||||
index: number = -1;
|
||||
|
||||
constructor(public input: string) {
|
||||
this.length = input.length;
|
||||
this.advance();
|
||||
}
|
||||
|
||||
advance() {
|
||||
this.peek =
|
||||
++this.index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, this.index);
|
||||
}
|
||||
|
||||
scanToken(): Token {
|
||||
var input = this.input, length = this.length, peek = this.peek, index = this.index;
|
||||
|
||||
// Skip whitespace.
|
||||
while (peek <= $SPACE) {
|
||||
if (++index >= length) {
|
||||
peek = $EOF;
|
||||
break;
|
||||
} else {
|
||||
peek = StringWrapper.charCodeAt(input, index);
|
||||
}
|
||||
}
|
||||
|
||||
this.peek = peek;
|
||||
this.index = index;
|
||||
|
||||
if (index >= length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle identifiers and numbers.
|
||||
if (isIdentifierStart(peek)) return this.scanIdentifier();
|
||||
if (isDigit(peek)) return this.scanNumber(index);
|
||||
|
||||
var start: number = index;
|
||||
switch (peek) {
|
||||
case $PERIOD:
|
||||
this.advance();
|
||||
return isDigit(this.peek) ? this.scanNumber(start) : newCharacterToken(start, $PERIOD);
|
||||
case $LPAREN:
|
||||
case $RPAREN:
|
||||
case $LBRACE:
|
||||
case $RBRACE:
|
||||
case $LBRACKET:
|
||||
case $RBRACKET:
|
||||
case $COMMA:
|
||||
case $COLON:
|
||||
case $SEMICOLON:
|
||||
return this.scanCharacter(start, peek);
|
||||
case $SQ:
|
||||
case $DQ:
|
||||
return this.scanString();
|
||||
case $HASH:
|
||||
case $PLUS:
|
||||
case $MINUS:
|
||||
case $STAR:
|
||||
case $SLASH:
|
||||
case $PERCENT:
|
||||
case $CARET:
|
||||
return this.scanOperator(start, StringWrapper.fromCharCode(peek));
|
||||
case $QUESTION:
|
||||
return this.scanComplexOperator(start, '?', $PERIOD, '.');
|
||||
case $LT:
|
||||
case $GT:
|
||||
return this.scanComplexOperator(start, StringWrapper.fromCharCode(peek), $EQ, '=');
|
||||
case $BANG:
|
||||
case $EQ:
|
||||
return this.scanComplexOperator(start, StringWrapper.fromCharCode(peek), $EQ, '=', $EQ,
|
||||
'=');
|
||||
case $AMPERSAND:
|
||||
return this.scanComplexOperator(start, '&', $AMPERSAND, '&');
|
||||
case $BAR:
|
||||
return this.scanComplexOperator(start, '|', $BAR, '|');
|
||||
case $NBSP:
|
||||
while (isWhitespace(this.peek)) this.advance();
|
||||
return this.scanToken();
|
||||
}
|
||||
|
||||
this.error(`Unexpected character [${StringWrapper.fromCharCode(peek)}]`, 0);
|
||||
return null;
|
||||
}
|
||||
|
||||
scanCharacter(start: number, code: number): Token {
|
||||
assert(this.peek == code);
|
||||
this.advance();
|
||||
return newCharacterToken(start, code);
|
||||
}
|
||||
|
||||
|
||||
scanOperator(start: number, str: string): Token {
|
||||
assert(this.peek == StringWrapper.charCodeAt(str, 0));
|
||||
assert(SetWrapper.has(OPERATORS, str));
|
||||
this.advance();
|
||||
return newOperatorToken(start, str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenize a 2/3 char long operator
|
||||
*
|
||||
* @param start start index in the expression
|
||||
* @param one first symbol (always part of the operator)
|
||||
* @param twoCode code point for the second symbol
|
||||
* @param two second symbol (part of the operator when the second code point matches)
|
||||
* @param threeCode code point for the third symbol
|
||||
* @param three third symbol (part of the operator when provided and matches source expression)
|
||||
* @returns {Token}
|
||||
*/
|
||||
scanComplexOperator(start: number, one: string, twoCode: number, two: string, threeCode?: number,
|
||||
three?: string): Token {
|
||||
assert(this.peek == StringWrapper.charCodeAt(one, 0));
|
||||
this.advance();
|
||||
var str: string = one;
|
||||
if (this.peek == twoCode) {
|
||||
this.advance();
|
||||
str += two;
|
||||
}
|
||||
if (isPresent(threeCode) && this.peek == threeCode) {
|
||||
this.advance();
|
||||
str += three;
|
||||
}
|
||||
assert(SetWrapper.has(OPERATORS, str));
|
||||
return newOperatorToken(start, str);
|
||||
}
|
||||
|
||||
scanIdentifier(): Token {
|
||||
assert(isIdentifierStart(this.peek));
|
||||
var start: number = this.index;
|
||||
this.advance();
|
||||
while (isIdentifierPart(this.peek)) this.advance();
|
||||
var str: string = this.input.substring(start, this.index);
|
||||
if (SetWrapper.has(KEYWORDS, str)) {
|
||||
return newKeywordToken(start, str);
|
||||
} else {
|
||||
return newIdentifierToken(start, str);
|
||||
}
|
||||
}
|
||||
|
||||
scanNumber(start: number): Token {
|
||||
assert(isDigit(this.peek));
|
||||
var simple: boolean = (this.index === start);
|
||||
this.advance(); // Skip initial digit.
|
||||
while (true) {
|
||||
if (isDigit(this.peek)) {
|
||||
// Do nothing.
|
||||
} else if (this.peek == $PERIOD) {
|
||||
simple = false;
|
||||
} else if (isExponentStart(this.peek)) {
|
||||
this.advance();
|
||||
if (isExponentSign(this.peek)) this.advance();
|
||||
if (!isDigit(this.peek)) this.error('Invalid exponent', -1);
|
||||
simple = false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
this.advance();
|
||||
}
|
||||
var str: string = this.input.substring(start, this.index);
|
||||
// TODO
|
||||
var value: number =
|
||||
simple ? NumberWrapper.parseIntAutoRadix(str) : NumberWrapper.parseFloat(str);
|
||||
return newNumberToken(start, value);
|
||||
}
|
||||
|
||||
scanString(): Token {
|
||||
assert(this.peek == $SQ || this.peek == $DQ);
|
||||
var start: number = this.index;
|
||||
var quote: number = this.peek;
|
||||
this.advance(); // Skip initial quote.
|
||||
|
||||
var buffer: StringJoiner;
|
||||
var marker: number = this.index;
|
||||
var input: string = this.input;
|
||||
|
||||
while (this.peek != quote) {
|
||||
if (this.peek == $BACKSLASH) {
|
||||
if (buffer == null) buffer = new StringJoiner();
|
||||
buffer.add(input.substring(marker, this.index));
|
||||
this.advance();
|
||||
var unescapedCode: number;
|
||||
if (this.peek == $u) {
|
||||
// 4 character hex code for unicode character.
|
||||
var hex: string = input.substring(this.index + 1, this.index + 5);
|
||||
try {
|
||||
unescapedCode = NumberWrapper.parseInt(hex, 16);
|
||||
} catch (e) {
|
||||
this.error(`Invalid unicode escape [\\u${hex}]`, 0);
|
||||
}
|
||||
for (var i: number = 0; i < 5; i++) {
|
||||
this.advance();
|
||||
}
|
||||
} else {
|
||||
unescapedCode = unescape(this.peek);
|
||||
this.advance();
|
||||
}
|
||||
buffer.add(StringWrapper.fromCharCode(unescapedCode));
|
||||
marker = this.index;
|
||||
} else if (this.peek == $EOF) {
|
||||
this.error('Unterminated quote', 0);
|
||||
} else {
|
||||
this.advance();
|
||||
}
|
||||
}
|
||||
|
||||
var last: string = input.substring(marker, this.index);
|
||||
this.advance(); // Skip terminating quote.
|
||||
|
||||
// Compute the unescaped string value.
|
||||
var unescaped: string = last;
|
||||
if (buffer != null) {
|
||||
buffer.add(last);
|
||||
unescaped = buffer.toString();
|
||||
}
|
||||
return newStringToken(start, unescaped);
|
||||
}
|
||||
|
||||
error(message: string, offset: number) {
|
||||
var position: number = this.index + offset;
|
||||
throw new ScannerError(
|
||||
`Lexer Error: ${message} at column ${position} in expression [${this.input}]`);
|
||||
}
|
||||
}
|
||||
|
||||
function isWhitespace(code: number): boolean {
|
||||
return (code >= $TAB && code <= $SPACE) || (code == $NBSP);
|
||||
}
|
||||
|
||||
function isIdentifierStart(code: number): boolean {
|
||||
return ($a <= code && code <= $z) || ($A <= code && code <= $Z) || (code == $_) || (code == $$);
|
||||
}
|
||||
|
||||
function isIdentifierPart(code: number): boolean {
|
||||
return ($a <= code && code <= $z) || ($A <= code && code <= $Z) || ($0 <= code && code <= $9) ||
|
||||
(code == $_) || (code == $$);
|
||||
}
|
||||
|
||||
function isDigit(code: number): boolean {
|
||||
return $0 <= code && code <= $9;
|
||||
}
|
||||
|
||||
function isExponentStart(code: number): boolean {
|
||||
return code == $e || code == $E;
|
||||
}
|
||||
|
||||
function isExponentSign(code: number): boolean {
|
||||
return code == $MINUS || code == $PLUS;
|
||||
}
|
||||
|
||||
function unescape(code: number): number {
|
||||
switch (code) {
|
||||
case $n:
|
||||
return $LF;
|
||||
case $f:
|
||||
return $FF;
|
||||
case $r:
|
||||
return $CR;
|
||||
case $t:
|
||||
return $TAB;
|
||||
case $v:
|
||||
return $VTAB;
|
||||
default:
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
var OPERATORS = SetWrapper.createFromList([
|
||||
'+',
|
||||
'-',
|
||||
'*',
|
||||
'/',
|
||||
'%',
|
||||
'^',
|
||||
'=',
|
||||
'==',
|
||||
'!=',
|
||||
'===',
|
||||
'!==',
|
||||
'<',
|
||||
'>',
|
||||
'<=',
|
||||
'>=',
|
||||
'&&',
|
||||
'||',
|
||||
'&',
|
||||
'|',
|
||||
'!',
|
||||
'?',
|
||||
'#',
|
||||
'?.'
|
||||
]);
|
||||
|
||||
|
||||
var KEYWORDS =
|
||||
SetWrapper.createFromList(['var', 'null', 'undefined', 'true', 'false', 'if', 'else']);
|
44
modules/angular2/src/core/change_detection/parser/locals.ts
Normal file
44
modules/angular2/src/core/change_detection/parser/locals.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import {isPresent, BaseException} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
export class Locals {
|
||||
constructor(public parent: Locals, public current: Map<any, any>) {}
|
||||
|
||||
contains(name: string): boolean {
|
||||
if (this.current.has(name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isPresent(this.parent)) {
|
||||
return this.parent.contains(name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get(name: string): any {
|
||||
if (this.current.has(name)) {
|
||||
return this.current.get(name);
|
||||
}
|
||||
|
||||
if (isPresent(this.parent)) {
|
||||
return this.parent.get(name);
|
||||
}
|
||||
|
||||
throw new BaseException(`Cannot find '${name}'`);
|
||||
}
|
||||
|
||||
set(name: string, value: any): void {
|
||||
// TODO(rado): consider removing this check if we can guarantee this is not
|
||||
// exposed to the public API.
|
||||
// TODO: vsavkin maybe it should check only the local map
|
||||
if (this.current.has(name)) {
|
||||
this.current.set(name, value);
|
||||
} else {
|
||||
throw new BaseException(
|
||||
`Setting of new keys post-construction is not supported. Key: ${name}.`);
|
||||
}
|
||||
}
|
||||
|
||||
clearValues(): void { MapWrapper.clearValues(this.current); }
|
||||
}
|
691
modules/angular2/src/core/change_detection/parser/parser.ts
Normal file
691
modules/angular2/src/core/change_detection/parser/parser.ts
Normal file
@ -0,0 +1,691 @@
|
||||
import {Injectable} from 'angular2/src/di/decorators';
|
||||
import {isBlank, isPresent, BaseException, StringWrapper} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, List} from 'angular2/src/facade/collection';
|
||||
import {
|
||||
Lexer,
|
||||
EOF,
|
||||
Token,
|
||||
$PERIOD,
|
||||
$COLON,
|
||||
$SEMICOLON,
|
||||
$LBRACKET,
|
||||
$RBRACKET,
|
||||
$COMMA,
|
||||
$LBRACE,
|
||||
$RBRACE,
|
||||
$LPAREN,
|
||||
$RPAREN
|
||||
} from './lexer';
|
||||
import {reflector, Reflector} from 'angular2/src/reflection/reflection';
|
||||
import {
|
||||
AST,
|
||||
EmptyExpr,
|
||||
ImplicitReceiver,
|
||||
PropertyRead,
|
||||
PropertyWrite,
|
||||
SafePropertyRead,
|
||||
LiteralPrimitive,
|
||||
Binary,
|
||||
PrefixNot,
|
||||
Conditional,
|
||||
If,
|
||||
BindingPipe,
|
||||
Chain,
|
||||
KeyedRead,
|
||||
KeyedWrite,
|
||||
LiteralArray,
|
||||
LiteralMap,
|
||||
Interpolation,
|
||||
MethodCall,
|
||||
SafeMethodCall,
|
||||
FunctionCall,
|
||||
TemplateBinding,
|
||||
ASTWithSource,
|
||||
AstVisitor
|
||||
} from './ast';
|
||||
|
||||
|
||||
var _implicitReceiver = new ImplicitReceiver();
|
||||
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
|
||||
var INTERPOLATION_REGEXP = /\{\{(.*?)\}\}/g;
|
||||
|
||||
class ParseException extends BaseException {
|
||||
constructor(message: string, input: string, errLocation: string, ctxLocation?: any) {
|
||||
super(`Parser Error: ${message} ${errLocation} [${input}] in ${ctxLocation}`, null, null,
|
||||
ctxLocation);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class Parser {
|
||||
_reflector: Reflector;
|
||||
|
||||
constructor(public _lexer: Lexer, providedReflector: Reflector = null) {
|
||||
this._reflector = isPresent(providedReflector) ? providedReflector : reflector;
|
||||
}
|
||||
|
||||
parseAction(input: string, location: any): ASTWithSource {
|
||||
this._checkNoInterpolation(input, location);
|
||||
var tokens = this._lexer.tokenize(input);
|
||||
var ast = new _ParseAST(input, location, tokens, this._reflector, true).parseChain();
|
||||
return new ASTWithSource(ast, input, location);
|
||||
}
|
||||
|
||||
parseBinding(input: string, location: any): ASTWithSource {
|
||||
this._checkNoInterpolation(input, location);
|
||||
var tokens = this._lexer.tokenize(input);
|
||||
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseChain();
|
||||
return new ASTWithSource(ast, input, location);
|
||||
}
|
||||
|
||||
parseSimpleBinding(input: string, location: string): ASTWithSource {
|
||||
this._checkNoInterpolation(input, location);
|
||||
var tokens = this._lexer.tokenize(input);
|
||||
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseSimpleBinding();
|
||||
return new ASTWithSource(ast, input, location);
|
||||
}
|
||||
|
||||
parseTemplateBindings(input: string, location: any): List<TemplateBinding> {
|
||||
var tokens = this._lexer.tokenize(input);
|
||||
return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings();
|
||||
}
|
||||
|
||||
parseInterpolation(input: string, location: any): ASTWithSource {
|
||||
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
|
||||
if (parts.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
var strings = [];
|
||||
var expressions = [];
|
||||
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var part: string = parts[i];
|
||||
if (i % 2 === 0) {
|
||||
// fixed string
|
||||
strings.push(part);
|
||||
} else if (part.trim().length > 0) {
|
||||
var tokens = this._lexer.tokenize(part);
|
||||
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseChain();
|
||||
expressions.push(ast);
|
||||
} else {
|
||||
throw new ParseException('Blank expressions are not allowed in interpolated strings', input,
|
||||
`at column ${this._findInterpolationErrorColumn(parts, i)} in`,
|
||||
location);
|
||||
}
|
||||
}
|
||||
return new ASTWithSource(new Interpolation(strings, expressions), input, location);
|
||||
}
|
||||
|
||||
wrapLiteralPrimitive(input: string, location: any): ASTWithSource {
|
||||
return new ASTWithSource(new LiteralPrimitive(input), input, location);
|
||||
}
|
||||
|
||||
private _checkNoInterpolation(input: string, location: any): void {
|
||||
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
|
||||
if (parts.length > 1) {
|
||||
throw new ParseException('Got interpolation ({{}}) where expression was expected', input,
|
||||
`at column ${this._findInterpolationErrorColumn(parts, 1)} in`,
|
||||
location);
|
||||
}
|
||||
}
|
||||
|
||||
private _findInterpolationErrorColumn(parts: string[], partInErrIdx: number): number {
|
||||
var errLocation = '';
|
||||
for (var j = 0; j < partInErrIdx; j++) {
|
||||
errLocation += j % 2 === 0 ? parts[j] : `{{${parts[j]}}}`;
|
||||
}
|
||||
|
||||
return errLocation.length;
|
||||
}
|
||||
}
|
||||
|
||||
export class _ParseAST {
|
||||
index: number = 0;
|
||||
constructor(public input: string, public location: any, public tokens: List<any>,
|
||||
public reflector: Reflector, public parseAction: boolean) {}
|
||||
|
||||
peek(offset: number): Token {
|
||||
var i = this.index + offset;
|
||||
return i < this.tokens.length ? this.tokens[i] : EOF;
|
||||
}
|
||||
|
||||
get next(): Token { return this.peek(0); }
|
||||
|
||||
get inputIndex(): number {
|
||||
return (this.index < this.tokens.length) ? this.next.index : this.input.length;
|
||||
}
|
||||
|
||||
advance() { this.index++; }
|
||||
|
||||
optionalCharacter(code: number): boolean {
|
||||
if (this.next.isCharacter(code)) {
|
||||
this.advance();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
optionalKeywordVar(): boolean {
|
||||
if (this.peekKeywordVar()) {
|
||||
this.advance();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
peekKeywordVar(): boolean { return this.next.isKeywordVar() || this.next.isOperator('#'); }
|
||||
|
||||
expectCharacter(code: number) {
|
||||
if (this.optionalCharacter(code)) return;
|
||||
this.error(`Missing expected ${StringWrapper.fromCharCode(code)}`);
|
||||
}
|
||||
|
||||
|
||||
optionalOperator(op: string): boolean {
|
||||
if (this.next.isOperator(op)) {
|
||||
this.advance();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
expectOperator(operator: string) {
|
||||
if (this.optionalOperator(operator)) return;
|
||||
this.error(`Missing expected operator ${operator}`);
|
||||
}
|
||||
|
||||
expectIdentifierOrKeyword(): string {
|
||||
var n = this.next;
|
||||
if (!n.isIdentifier() && !n.isKeyword()) {
|
||||
this.error(`Unexpected token ${n}, expected identifier or keyword`);
|
||||
}
|
||||
this.advance();
|
||||
return n.toString();
|
||||
}
|
||||
|
||||
expectIdentifierOrKeywordOrString(): string {
|
||||
var n = this.next;
|
||||
if (!n.isIdentifier() && !n.isKeyword() && !n.isString()) {
|
||||
this.error(`Unexpected token ${n}, expected identifier, keyword, or string`);
|
||||
}
|
||||
this.advance();
|
||||
return n.toString();
|
||||
}
|
||||
|
||||
parseChain(): AST {
|
||||
var exprs = [];
|
||||
while (this.index < this.tokens.length) {
|
||||
var expr = this.parsePipe();
|
||||
exprs.push(expr);
|
||||
|
||||
if (this.optionalCharacter($SEMICOLON)) {
|
||||
if (!this.parseAction) {
|
||||
this.error("Binding expression cannot contain chained expression");
|
||||
}
|
||||
while (this.optionalCharacter($SEMICOLON)) {
|
||||
} // read all semicolons
|
||||
} else if (this.index < this.tokens.length) {
|
||||
this.error(`Unexpected token '${this.next}'`);
|
||||
}
|
||||
}
|
||||
if (exprs.length == 0) return new EmptyExpr();
|
||||
if (exprs.length == 1) return exprs[0];
|
||||
return new Chain(exprs);
|
||||
}
|
||||
|
||||
parseSimpleBinding(): AST {
|
||||
var ast = this.parseChain();
|
||||
if (!SimpleExpressionChecker.check(ast)) {
|
||||
this.error(`Simple binding expression can only contain field access and constants'`);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
parsePipe(): AST {
|
||||
var result = this.parseExpression();
|
||||
if (this.optionalOperator("|")) {
|
||||
if (this.parseAction) {
|
||||
this.error("Cannot have a pipe in an action expression");
|
||||
}
|
||||
|
||||
do {
|
||||
var name = this.expectIdentifierOrKeyword();
|
||||
var args = [];
|
||||
while (this.optionalCharacter($COLON)) {
|
||||
args.push(this.parsePipe());
|
||||
}
|
||||
result = new BindingPipe(result, name, args);
|
||||
} while (this.optionalOperator("|"));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
parseExpression(): AST { return this.parseConditional(); }
|
||||
|
||||
parseConditional(): AST {
|
||||
var start = this.inputIndex;
|
||||
var result = this.parseLogicalOr();
|
||||
|
||||
if (this.optionalOperator('?')) {
|
||||
var yes = this.parsePipe();
|
||||
if (!this.optionalCharacter($COLON)) {
|
||||
var end = this.inputIndex;
|
||||
var expression = this.input.substring(start, end);
|
||||
this.error(`Conditional expression ${expression} requires all 3 expressions`);
|
||||
}
|
||||
var no = this.parsePipe();
|
||||
return new Conditional(result, yes, no);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
parseLogicalOr(): AST {
|
||||
// '||'
|
||||
var result = this.parseLogicalAnd();
|
||||
while (this.optionalOperator('||')) {
|
||||
result = new Binary('||', result, this.parseLogicalAnd());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseLogicalAnd(): AST {
|
||||
// '&&'
|
||||
var result = this.parseEquality();
|
||||
while (this.optionalOperator('&&')) {
|
||||
result = new Binary('&&', result, this.parseEquality());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseEquality(): AST {
|
||||
// '==','!=','===','!=='
|
||||
var result = this.parseRelational();
|
||||
while (true) {
|
||||
if (this.optionalOperator('==')) {
|
||||
result = new Binary('==', result, this.parseRelational());
|
||||
} else if (this.optionalOperator('===')) {
|
||||
result = new Binary('===', result, this.parseRelational());
|
||||
} else if (this.optionalOperator('!=')) {
|
||||
result = new Binary('!=', result, this.parseRelational());
|
||||
} else if (this.optionalOperator('!==')) {
|
||||
result = new Binary('!==', result, this.parseRelational());
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parseRelational(): AST {
|
||||
// '<', '>', '<=', '>='
|
||||
var result = this.parseAdditive();
|
||||
while (true) {
|
||||
if (this.optionalOperator('<')) {
|
||||
result = new Binary('<', result, this.parseAdditive());
|
||||
} else if (this.optionalOperator('>')) {
|
||||
result = new Binary('>', result, this.parseAdditive());
|
||||
} else if (this.optionalOperator('<=')) {
|
||||
result = new Binary('<=', result, this.parseAdditive());
|
||||
} else if (this.optionalOperator('>=')) {
|
||||
result = new Binary('>=', result, this.parseAdditive());
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parseAdditive(): AST {
|
||||
// '+', '-'
|
||||
var result = this.parseMultiplicative();
|
||||
while (true) {
|
||||
if (this.optionalOperator('+')) {
|
||||
result = new Binary('+', result, this.parseMultiplicative());
|
||||
} else if (this.optionalOperator('-')) {
|
||||
result = new Binary('-', result, this.parseMultiplicative());
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parseMultiplicative(): AST {
|
||||
// '*', '%', '/'
|
||||
var result = this.parsePrefix();
|
||||
while (true) {
|
||||
if (this.optionalOperator('*')) {
|
||||
result = new Binary('*', result, this.parsePrefix());
|
||||
} else if (this.optionalOperator('%')) {
|
||||
result = new Binary('%', result, this.parsePrefix());
|
||||
} else if (this.optionalOperator('/')) {
|
||||
result = new Binary('/', result, this.parsePrefix());
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parsePrefix(): AST {
|
||||
if (this.optionalOperator('+')) {
|
||||
return this.parsePrefix();
|
||||
} else if (this.optionalOperator('-')) {
|
||||
return new Binary('-', new LiteralPrimitive(0), this.parsePrefix());
|
||||
} else if (this.optionalOperator('!')) {
|
||||
return new PrefixNot(this.parsePrefix());
|
||||
} else {
|
||||
return this.parseCallChain();
|
||||
}
|
||||
}
|
||||
|
||||
parseCallChain(): AST {
|
||||
var result = this.parsePrimary();
|
||||
while (true) {
|
||||
if (this.optionalCharacter($PERIOD)) {
|
||||
result = this.parseAccessMemberOrMethodCall(result, false);
|
||||
|
||||
} else if (this.optionalOperator('?.')) {
|
||||
result = this.parseAccessMemberOrMethodCall(result, true);
|
||||
|
||||
} else if (this.optionalCharacter($LBRACKET)) {
|
||||
var key = this.parsePipe();
|
||||
this.expectCharacter($RBRACKET);
|
||||
if (this.optionalOperator("=")) {
|
||||
var value = this.parseConditional();
|
||||
result = new KeyedWrite(result, key, value);
|
||||
} else {
|
||||
result = new KeyedRead(result, key);
|
||||
}
|
||||
|
||||
} else if (this.optionalCharacter($LPAREN)) {
|
||||
var args = this.parseCallArguments();
|
||||
this.expectCharacter($RPAREN);
|
||||
result = new FunctionCall(result, args);
|
||||
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parsePrimary(): AST {
|
||||
if (this.optionalCharacter($LPAREN)) {
|
||||
let result = this.parsePipe();
|
||||
this.expectCharacter($RPAREN);
|
||||
return result;
|
||||
} else if (this.next.isKeywordNull() || this.next.isKeywordUndefined()) {
|
||||
this.advance();
|
||||
return new LiteralPrimitive(null);
|
||||
|
||||
} else if (this.next.isKeywordTrue()) {
|
||||
this.advance();
|
||||
return new LiteralPrimitive(true);
|
||||
|
||||
} else if (this.next.isKeywordFalse()) {
|
||||
this.advance();
|
||||
return new LiteralPrimitive(false);
|
||||
|
||||
} else if (this.parseAction && this.next.isKeywordIf()) {
|
||||
this.advance();
|
||||
this.expectCharacter($LPAREN);
|
||||
let condition = this.parseExpression();
|
||||
this.expectCharacter($RPAREN);
|
||||
let ifExp = this.parseExpressionOrBlock();
|
||||
let elseExp;
|
||||
if (this.next.isKeywordElse()) {
|
||||
this.advance();
|
||||
elseExp = this.parseExpressionOrBlock();
|
||||
}
|
||||
return new If(condition, ifExp, elseExp);
|
||||
|
||||
} else if (this.optionalCharacter($LBRACKET)) {
|
||||
var elements = this.parseExpressionList($RBRACKET);
|
||||
this.expectCharacter($RBRACKET);
|
||||
return new LiteralArray(elements);
|
||||
|
||||
} else if (this.next.isCharacter($LBRACE)) {
|
||||
return this.parseLiteralMap();
|
||||
|
||||
} else if (this.next.isIdentifier()) {
|
||||
return this.parseAccessMemberOrMethodCall(_implicitReceiver, false);
|
||||
|
||||
} else if (this.next.isNumber()) {
|
||||
var value = this.next.toNumber();
|
||||
this.advance();
|
||||
return new LiteralPrimitive(value);
|
||||
|
||||
} else if (this.next.isString()) {
|
||||
var literalValue = this.next.toString();
|
||||
this.advance();
|
||||
return new LiteralPrimitive(literalValue);
|
||||
|
||||
} else if (this.index >= this.tokens.length) {
|
||||
this.error(`Unexpected end of expression: ${this.input}`);
|
||||
|
||||
} else {
|
||||
this.error(`Unexpected token ${this.next}`);
|
||||
}
|
||||
// error() throws, so we don't reach here.
|
||||
throw new BaseException("Fell through all cases in parsePrimary");
|
||||
}
|
||||
|
||||
parseExpressionList(terminator: number): List<any> {
|
||||
var result = [];
|
||||
if (!this.next.isCharacter(terminator)) {
|
||||
do {
|
||||
result.push(this.parsePipe());
|
||||
} while (this.optionalCharacter($COMMA));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseLiteralMap(): LiteralMap {
|
||||
var keys = [];
|
||||
var values = [];
|
||||
this.expectCharacter($LBRACE);
|
||||
if (!this.optionalCharacter($RBRACE)) {
|
||||
do {
|
||||
var key = this.expectIdentifierOrKeywordOrString();
|
||||
keys.push(key);
|
||||
this.expectCharacter($COLON);
|
||||
values.push(this.parsePipe());
|
||||
} while (this.optionalCharacter($COMMA));
|
||||
this.expectCharacter($RBRACE);
|
||||
}
|
||||
return new LiteralMap(keys, values);
|
||||
}
|
||||
|
||||
parseAccessMemberOrMethodCall(receiver: AST, isSafe: boolean = false): AST {
|
||||
let id = this.expectIdentifierOrKeyword();
|
||||
|
||||
if (this.optionalCharacter($LPAREN)) {
|
||||
let args = this.parseCallArguments();
|
||||
this.expectCharacter($RPAREN);
|
||||
let fn = this.reflector.method(id);
|
||||
return isSafe ? new SafeMethodCall(receiver, id, fn, args) :
|
||||
new MethodCall(receiver, id, fn, args);
|
||||
|
||||
} else {
|
||||
if (isSafe) {
|
||||
if (this.optionalOperator("=")) {
|
||||
this.error("The '?.' operator cannot be used in the assignment");
|
||||
} else {
|
||||
return new SafePropertyRead(receiver, id, this.reflector.getter(id));
|
||||
}
|
||||
} else {
|
||||
if (this.optionalOperator("=")) {
|
||||
if (!this.parseAction) {
|
||||
this.error("Bindings cannot contain assignments");
|
||||
}
|
||||
|
||||
let value = this.parseConditional();
|
||||
return new PropertyWrite(receiver, id, this.reflector.setter(id), value);
|
||||
} else {
|
||||
return new PropertyRead(receiver, id, this.reflector.getter(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
parseCallArguments(): BindingPipe[] {
|
||||
if (this.next.isCharacter($RPAREN)) return [];
|
||||
var positionals = [];
|
||||
do {
|
||||
positionals.push(this.parsePipe());
|
||||
} while (this.optionalCharacter($COMMA));
|
||||
return positionals;
|
||||
}
|
||||
|
||||
parseExpressionOrBlock(): AST {
|
||||
if (this.optionalCharacter($LBRACE)) {
|
||||
let block = this.parseBlockContent();
|
||||
this.expectCharacter($RBRACE);
|
||||
return block;
|
||||
}
|
||||
|
||||
return this.parseExpression();
|
||||
}
|
||||
|
||||
parseBlockContent(): AST {
|
||||
if (!this.parseAction) {
|
||||
this.error("Binding expression cannot contain chained expression");
|
||||
}
|
||||
var exprs = [];
|
||||
while (this.index < this.tokens.length && !this.next.isCharacter($RBRACE)) {
|
||||
var expr = this.parseExpression();
|
||||
exprs.push(expr);
|
||||
|
||||
if (this.optionalCharacter($SEMICOLON)) {
|
||||
while (this.optionalCharacter($SEMICOLON)) {
|
||||
} // read all semicolons
|
||||
}
|
||||
}
|
||||
if (exprs.length == 0) return new EmptyExpr();
|
||||
if (exprs.length == 1) return exprs[0];
|
||||
|
||||
return new Chain(exprs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An identifier, a keyword, a string with an optional `-` inbetween.
|
||||
*/
|
||||
expectTemplateBindingKey(): string {
|
||||
var result = '';
|
||||
var operatorFound = false;
|
||||
do {
|
||||
result += this.expectIdentifierOrKeywordOrString();
|
||||
operatorFound = this.optionalOperator('-');
|
||||
if (operatorFound) {
|
||||
result += '-';
|
||||
}
|
||||
} while (operatorFound);
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
parseTemplateBindings(): any[] {
|
||||
var bindings = [];
|
||||
var prefix = null;
|
||||
while (this.index < this.tokens.length) {
|
||||
var keyIsVar: boolean = this.optionalKeywordVar();
|
||||
var key = this.expectTemplateBindingKey();
|
||||
if (!keyIsVar) {
|
||||
if (prefix == null) {
|
||||
prefix = key;
|
||||
} else {
|
||||
key = prefix + '-' + key;
|
||||
}
|
||||
}
|
||||
this.optionalCharacter($COLON);
|
||||
var name = null;
|
||||
var expression = null;
|
||||
if (keyIsVar) {
|
||||
if (this.optionalOperator("=")) {
|
||||
name = this.expectTemplateBindingKey();
|
||||
} else {
|
||||
name = '\$implicit';
|
||||
}
|
||||
} else if (this.next !== EOF && !this.peekKeywordVar()) {
|
||||
var start = this.inputIndex;
|
||||
var ast = this.parsePipe();
|
||||
var source = this.input.substring(start, this.inputIndex);
|
||||
expression = new ASTWithSource(ast, source, this.location);
|
||||
}
|
||||
bindings.push(new TemplateBinding(key, keyIsVar, name, expression));
|
||||
if (!this.optionalCharacter($SEMICOLON)) {
|
||||
this.optionalCharacter($COMMA);
|
||||
}
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
error(message: string, index: number = null) {
|
||||
if (isBlank(index)) index = this.index;
|
||||
|
||||
var location = (index < this.tokens.length) ? `at column ${this.tokens[index].index + 1} in` :
|
||||
`at the end of the expression`;
|
||||
|
||||
throw new ParseException(message, this.input, location, this.location);
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleExpressionChecker implements AstVisitor {
|
||||
static check(ast: AST): boolean {
|
||||
var s = new SimpleExpressionChecker();
|
||||
ast.visit(s);
|
||||
return s.simple;
|
||||
}
|
||||
|
||||
simple = true;
|
||||
|
||||
visitImplicitReceiver(ast: ImplicitReceiver) {}
|
||||
|
||||
visitInterpolation(ast: Interpolation) { this.simple = false; }
|
||||
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive) {}
|
||||
|
||||
visitPropertyRead(ast: PropertyRead) {}
|
||||
|
||||
visitPropertyWrite(ast: PropertyWrite) { this.simple = false; }
|
||||
|
||||
visitSafePropertyRead(ast: SafePropertyRead) { this.simple = false; }
|
||||
|
||||
visitMethodCall(ast: MethodCall) { this.simple = false; }
|
||||
|
||||
visitSafeMethodCall(ast: SafeMethodCall) { this.simple = false; }
|
||||
|
||||
visitFunctionCall(ast: FunctionCall) { this.simple = false; }
|
||||
|
||||
visitLiteralArray(ast: LiteralArray) { this.visitAll(ast.expressions); }
|
||||
|
||||
visitLiteralMap(ast: LiteralMap) { this.visitAll(ast.values); }
|
||||
|
||||
visitBinary(ast: Binary) { this.simple = false; }
|
||||
|
||||
visitPrefixNot(ast: PrefixNot) { this.simple = false; }
|
||||
|
||||
visitConditional(ast: Conditional) { this.simple = false; }
|
||||
|
||||
visitPipe(ast: BindingPipe) { this.simple = false; }
|
||||
|
||||
visitKeyedRead(ast: KeyedRead) { this.simple = false; }
|
||||
|
||||
visitKeyedWrite(ast: KeyedWrite) { this.simple = false; }
|
||||
|
||||
visitAll(asts: List<any>): List<any> {
|
||||
var res = ListWrapper.createFixedSize(asts.length);
|
||||
for (var i = 0; i < asts.length; ++i) {
|
||||
res[i] = asts[i].visit(this);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
visitChain(ast: Chain) { this.simple = false; }
|
||||
|
||||
visitIf(ast: If) { this.simple = false; }
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
library angular2.core.compiler.pipe_lifecycle_reflector;
|
||||
|
||||
import 'package:angular2/src/change_detection/pipe_transform.dart';
|
||||
|
||||
bool implementsOnDestroy(Object pipe) => pipe is PipeOnDestroy;
|
@ -0,0 +1,3 @@
|
||||
export function implementsOnDestroy(pipe: any): boolean {
|
||||
return pipe.constructor.prototype.onDestroy;
|
||||
}
|
39
modules/angular2/src/core/change_detection/pipe_transform.ts
Normal file
39
modules/angular2/src/core/change_detection/pipe_transform.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import {ABSTRACT, BaseException, CONST, Type} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
* An interface which all pipes must implement.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* class DoublePipe implements PipeTransform {
|
||||
* transform(value, args = []) {
|
||||
* return `${value}${value}`;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface PipeTransform { transform(value: any, args: List<any>): any; }
|
||||
|
||||
/**
|
||||
* An interface that stateful pipes should implement.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* class StatefulPipe implements PipeTransform, PipeOnDestroy {
|
||||
* connection;
|
||||
*
|
||||
* onDestroy() {
|
||||
* this.connection.release();
|
||||
* }
|
||||
*
|
||||
* transform(value, args = []) {
|
||||
* this.connection = createConnection();
|
||||
* // ...
|
||||
* return someValue;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface PipeOnDestroy { onDestroy(): void; }
|
3
modules/angular2/src/core/change_detection/pipes.ts
Normal file
3
modules/angular2/src/core/change_detection/pipes.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import {PipeTransform} from './pipe_transform';
|
||||
|
||||
export interface Pipes { get(name: string): PipeTransform; }
|
@ -0,0 +1,56 @@
|
||||
library angular2.src.change_detection.pregen_proto_change_detector;
|
||||
|
||||
import 'package:angular2/src/change_detection/interfaces.dart';
|
||||
import 'package:angular2/src/facade/lang.dart' show looseIdentical;
|
||||
|
||||
export 'dart:core' show List;
|
||||
export 'package:angular2/src/change_detection/abstract_change_detector.dart'
|
||||
show AbstractChangeDetector;
|
||||
export 'package:angular2/src/change_detection/change_detection.dart'
|
||||
show preGeneratedProtoDetectors;
|
||||
export 'package:angular2/src/change_detection/directive_record.dart'
|
||||
show DirectiveIndex, DirectiveRecord;
|
||||
export 'package:angular2/src/change_detection/interfaces.dart'
|
||||
show ChangeDetector, ChangeDetectorDefinition, ProtoChangeDetector;
|
||||
export 'package:angular2/src/change_detection/pipes.dart' show Pipes;
|
||||
export 'package:angular2/src/change_detection/proto_record.dart'
|
||||
show ProtoRecord;
|
||||
export 'package:angular2/src/change_detection/change_detection_util.dart'
|
||||
show ChangeDetectionUtil;
|
||||
export 'package:angular2/src/facade/lang.dart' show looseIdentical;
|
||||
|
||||
typedef ProtoChangeDetector PregenProtoChangeDetectorFactory(
|
||||
ChangeDetectorDefinition definition);
|
||||
|
||||
typedef ChangeDetector InstantiateMethod(dynamic dispatcher);
|
||||
|
||||
/// Implementation of [ProtoChangeDetector] for use by pre-generated change
|
||||
/// detectors in Angular 2 Dart.
|
||||
/// Classes generated by the `TemplateCompiler` use this. The `export`s above
|
||||
/// allow the generated code to `import` a single library and get all
|
||||
/// dependencies.
|
||||
class PregenProtoChangeDetector extends ProtoChangeDetector {
|
||||
/// The [ChangeDetectorDefinition#id]. Strictly informational.
|
||||
final String id;
|
||||
|
||||
/// Closure used to generate an actual [ChangeDetector].
|
||||
final InstantiateMethod _instantiateMethod;
|
||||
|
||||
/// Internal ctor.
|
||||
PregenProtoChangeDetector._(this.id, this._instantiateMethod);
|
||||
|
||||
static bool isSupported() => true;
|
||||
|
||||
factory PregenProtoChangeDetector(
|
||||
InstantiateMethod instantiateMethod, ChangeDetectorDefinition def) {
|
||||
return new PregenProtoChangeDetector._(def.id, instantiateMethod);
|
||||
}
|
||||
|
||||
@override
|
||||
instantiate(dynamic dispatcher) => _instantiateMethod(dispatcher);
|
||||
}
|
||||
|
||||
/// Provided as an optimization to cut down on '!' characters in generated
|
||||
/// change detectors. See https://github.com/angular/angular/issues/3248 for
|
||||
/// for details.
|
||||
bool looseNotIdentical(a, b) => !looseIdentical(a, b);
|
@ -0,0 +1,14 @@
|
||||
import {BaseException} from 'angular2/src/facade/lang';
|
||||
|
||||
import {ProtoChangeDetector, ChangeDetector} from './interfaces';
|
||||
import {coalesce} from './coalesce';
|
||||
|
||||
export {Function as PregenProtoChangeDetectorFactory};
|
||||
|
||||
export class PregenProtoChangeDetector implements ProtoChangeDetector {
|
||||
static isSupported(): boolean { return false; }
|
||||
|
||||
instantiate(dispatcher: any): ChangeDetector {
|
||||
throw new BaseException('Pregen change detection not supported in Js');
|
||||
}
|
||||
}
|
@ -0,0 +1,435 @@
|
||||
import {BaseException, Type, isBlank, isPresent, isString} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {
|
||||
PropertyRead,
|
||||
PropertyWrite,
|
||||
KeyedWrite,
|
||||
AST,
|
||||
ASTWithSource,
|
||||
AstVisitor,
|
||||
Binary,
|
||||
Chain,
|
||||
Conditional,
|
||||
If,
|
||||
BindingPipe,
|
||||
FunctionCall,
|
||||
ImplicitReceiver,
|
||||
Interpolation,
|
||||
KeyedRead,
|
||||
LiteralArray,
|
||||
LiteralMap,
|
||||
LiteralPrimitive,
|
||||
MethodCall,
|
||||
PrefixNot,
|
||||
SafePropertyRead,
|
||||
SafeMethodCall
|
||||
} from './parser/ast';
|
||||
|
||||
import {ChangeDetector, ProtoChangeDetector, ChangeDetectorDefinition} from './interfaces';
|
||||
import {ChangeDetectionUtil} from './change_detection_util';
|
||||
import {DynamicChangeDetector} from './dynamic_change_detector';
|
||||
import {BindingRecord, BindingTarget} from './binding_record';
|
||||
import {DirectiveRecord, DirectiveIndex} from './directive_record';
|
||||
import {EventBinding} from './event_binding';
|
||||
|
||||
import {coalesce} from './coalesce';
|
||||
import {ProtoRecord, RecordType} from './proto_record';
|
||||
|
||||
export class DynamicProtoChangeDetector implements ProtoChangeDetector {
|
||||
_propertyBindingRecords: ProtoRecord[];
|
||||
_propertyBindingTargets: BindingTarget[];
|
||||
_eventBindingRecords: EventBinding[];
|
||||
_directiveIndices: DirectiveIndex[];
|
||||
|
||||
constructor(private definition: ChangeDetectorDefinition) {
|
||||
this._propertyBindingRecords = createPropertyRecords(definition);
|
||||
this._eventBindingRecords = createEventRecords(definition);
|
||||
this._propertyBindingTargets = this.definition.bindingRecords.map(b => b.target);
|
||||
this._directiveIndices = this.definition.directiveRecords.map(d => d.directiveIndex);
|
||||
}
|
||||
|
||||
instantiate(dispatcher: any): ChangeDetector {
|
||||
return new DynamicChangeDetector(
|
||||
this.definition.id, dispatcher, this._propertyBindingRecords.length,
|
||||
this._propertyBindingTargets, this._directiveIndices, this.definition.strategy,
|
||||
this._propertyBindingRecords, this._eventBindingRecords, this.definition.directiveRecords,
|
||||
this.definition.genConfig);
|
||||
}
|
||||
}
|
||||
|
||||
export function createPropertyRecords(definition: ChangeDetectorDefinition): ProtoRecord[] {
|
||||
var recordBuilder = new ProtoRecordBuilder();
|
||||
ListWrapper.forEachWithIndex(definition.bindingRecords,
|
||||
(b, index) => recordBuilder.add(b, definition.variableNames, index));
|
||||
return coalesce(recordBuilder.records);
|
||||
}
|
||||
|
||||
export function createEventRecords(definition: ChangeDetectorDefinition): EventBinding[] {
|
||||
// TODO: vsavkin: remove $event when the compiler handles render-side variables properly
|
||||
var varNames = ListWrapper.concat(['$event'], definition.variableNames);
|
||||
return definition.eventRecords.map(er => {
|
||||
var records = _ConvertAstIntoProtoRecords.create(er, varNames);
|
||||
var dirIndex = er.implicitReceiver instanceof DirectiveIndex ? er.implicitReceiver : null;
|
||||
return new EventBinding(er.target.name, er.target.elementIndex, dirIndex, records);
|
||||
});
|
||||
}
|
||||
|
||||
export class ProtoRecordBuilder {
|
||||
records: List<ProtoRecord>;
|
||||
|
||||
constructor() { this.records = []; }
|
||||
|
||||
add(b: BindingRecord, variableNames: string[], bindingIndex: number) {
|
||||
var oldLast = ListWrapper.last(this.records);
|
||||
if (isPresent(oldLast) && oldLast.bindingRecord.directiveRecord == b.directiveRecord) {
|
||||
oldLast.lastInDirective = false;
|
||||
}
|
||||
var numberOfRecordsBefore = this.records.length;
|
||||
this._appendRecords(b, variableNames, bindingIndex);
|
||||
var newLast = ListWrapper.last(this.records);
|
||||
if (isPresent(newLast) && newLast !== oldLast) {
|
||||
newLast.lastInBinding = true;
|
||||
newLast.lastInDirective = true;
|
||||
this._setArgumentToPureFunction(numberOfRecordsBefore);
|
||||
}
|
||||
}
|
||||
|
||||
_setArgumentToPureFunction(startIndex: number): void {
|
||||
for (var i = startIndex; i < this.records.length; ++i) {
|
||||
var rec = this.records[i];
|
||||
if (rec.isPureFunction()) {
|
||||
rec.args.forEach(recordIndex => this.records[recordIndex - 1].argumentToPureFunction =
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_appendRecords(b: BindingRecord, variableNames: string[], bindingIndex: number) {
|
||||
if (b.isDirectiveLifecycle()) {
|
||||
this.records.push(new ProtoRecord(RecordType.DIRECTIVE_LIFECYCLE, b.lifecycleEvent, null, [],
|
||||
[], -1, null, this.records.length + 1, b, false, false,
|
||||
false, false, null));
|
||||
} else {
|
||||
_ConvertAstIntoProtoRecords.append(this.records, b, variableNames, bindingIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _ConvertAstIntoProtoRecords implements AstVisitor {
|
||||
constructor(private _records: List<ProtoRecord>, private _bindingRecord: BindingRecord,
|
||||
private _variableNames: string[], private _bindingIndex: number) {}
|
||||
|
||||
static append(records: List<ProtoRecord>, b: BindingRecord, variableNames: string[],
|
||||
bindingIndex: number) {
|
||||
var c = new _ConvertAstIntoProtoRecords(records, b, variableNames, bindingIndex);
|
||||
b.ast.visit(c);
|
||||
}
|
||||
|
||||
static create(b: BindingRecord, variableNames: List<any>): ProtoRecord[] {
|
||||
var rec = [];
|
||||
_ConvertAstIntoProtoRecords.append(rec, b, variableNames, null);
|
||||
rec[rec.length - 1].lastInBinding = true;
|
||||
return rec;
|
||||
}
|
||||
|
||||
visitImplicitReceiver(ast: ImplicitReceiver): any { return this._bindingRecord.implicitReceiver; }
|
||||
|
||||
visitInterpolation(ast: Interpolation): number {
|
||||
var args = this._visitAll(ast.expressions);
|
||||
return this._addRecord(RecordType.INTERPOLATE, "interpolate", _interpolationFn(ast.strings),
|
||||
args, ast.strings, 0);
|
||||
}
|
||||
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive): number {
|
||||
return this._addRecord(RecordType.CONST, "literal", ast.value, [], null, 0);
|
||||
}
|
||||
|
||||
visitPropertyRead(ast: PropertyRead): number {
|
||||
var receiver = ast.receiver.visit(this);
|
||||
if (isPresent(this._variableNames) && ListWrapper.contains(this._variableNames, ast.name) &&
|
||||
ast.receiver instanceof ImplicitReceiver) {
|
||||
return this._addRecord(RecordType.LOCAL, ast.name, ast.name, [], null, receiver);
|
||||
} else {
|
||||
return this._addRecord(RecordType.PROPERTY_READ, ast.name, ast.getter, [], null, receiver);
|
||||
}
|
||||
}
|
||||
|
||||
visitPropertyWrite(ast: PropertyWrite): number {
|
||||
if (isPresent(this._variableNames) && ListWrapper.contains(this._variableNames, ast.name) &&
|
||||
ast.receiver instanceof ImplicitReceiver) {
|
||||
throw new BaseException(`Cannot reassign a variable binding ${ast.name}`);
|
||||
} else {
|
||||
var receiver = ast.receiver.visit(this);
|
||||
var value = ast.value.visit(this);
|
||||
return this._addRecord(RecordType.PROPERTY_WRITE, ast.name, ast.setter, [value], null,
|
||||
receiver);
|
||||
}
|
||||
}
|
||||
|
||||
visitKeyedWrite(ast: KeyedWrite): number {
|
||||
var obj = ast.obj.visit(this);
|
||||
var key = ast.key.visit(this);
|
||||
var value = ast.value.visit(this);
|
||||
return this._addRecord(RecordType.KEYED_WRITE, null, null, [key, value], null, obj);
|
||||
}
|
||||
|
||||
visitSafePropertyRead(ast: SafePropertyRead): number {
|
||||
var receiver = ast.receiver.visit(this);
|
||||
return this._addRecord(RecordType.SAFE_PROPERTY, ast.name, ast.getter, [], null, receiver);
|
||||
}
|
||||
|
||||
visitMethodCall(ast: MethodCall): number {
|
||||
var receiver = ast.receiver.visit(this);
|
||||
var args = this._visitAll(ast.args);
|
||||
if (isPresent(this._variableNames) && ListWrapper.contains(this._variableNames, ast.name)) {
|
||||
var target = this._addRecord(RecordType.LOCAL, ast.name, ast.name, [], null, receiver);
|
||||
return this._addRecord(RecordType.INVOKE_CLOSURE, "closure", null, args, null, target);
|
||||
} else {
|
||||
return this._addRecord(RecordType.INVOKE_METHOD, ast.name, ast.fn, args, null, receiver);
|
||||
}
|
||||
}
|
||||
|
||||
visitSafeMethodCall(ast: SafeMethodCall): number {
|
||||
var receiver = ast.receiver.visit(this);
|
||||
var args = this._visitAll(ast.args);
|
||||
return this._addRecord(RecordType.SAFE_INVOKE_METHOD, ast.name, ast.fn, args, null, receiver);
|
||||
}
|
||||
|
||||
visitFunctionCall(ast: FunctionCall): number {
|
||||
var target = ast.target.visit(this);
|
||||
var args = this._visitAll(ast.args);
|
||||
return this._addRecord(RecordType.INVOKE_CLOSURE, "closure", null, args, null, target);
|
||||
}
|
||||
|
||||
visitLiteralArray(ast: LiteralArray): number {
|
||||
var primitiveName = `arrayFn${ast.expressions.length}`;
|
||||
return this._addRecord(RecordType.COLLECTION_LITERAL, primitiveName,
|
||||
_arrayFn(ast.expressions.length), this._visitAll(ast.expressions), null,
|
||||
0);
|
||||
}
|
||||
|
||||
visitLiteralMap(ast: LiteralMap): number {
|
||||
return this._addRecord(RecordType.COLLECTION_LITERAL, _mapPrimitiveName(ast.keys),
|
||||
ChangeDetectionUtil.mapFn(ast.keys), this._visitAll(ast.values), null,
|
||||
0);
|
||||
}
|
||||
|
||||
visitBinary(ast: Binary): number {
|
||||
var left = ast.left.visit(this);
|
||||
var right = ast.right.visit(this);
|
||||
return this._addRecord(RecordType.PRIMITIVE_OP, _operationToPrimitiveName(ast.operation),
|
||||
_operationToFunction(ast.operation), [left, right], null, 0);
|
||||
}
|
||||
|
||||
visitPrefixNot(ast: PrefixNot): number {
|
||||
var exp = ast.expression.visit(this);
|
||||
return this._addRecord(RecordType.PRIMITIVE_OP, "operation_negate",
|
||||
ChangeDetectionUtil.operation_negate, [exp], null, 0);
|
||||
}
|
||||
|
||||
visitConditional(ast: Conditional): number {
|
||||
var c = ast.condition.visit(this);
|
||||
var t = ast.trueExp.visit(this);
|
||||
var f = ast.falseExp.visit(this);
|
||||
return this._addRecord(RecordType.PRIMITIVE_OP, "cond", ChangeDetectionUtil.cond, [c, t, f],
|
||||
null, 0);
|
||||
}
|
||||
|
||||
visitPipe(ast: BindingPipe): number {
|
||||
var value = ast.exp.visit(this);
|
||||
var args = this._visitAll(ast.args);
|
||||
return this._addRecord(RecordType.PIPE, ast.name, ast.name, args, null, value);
|
||||
}
|
||||
|
||||
visitKeyedRead(ast: KeyedRead): number {
|
||||
var obj = ast.obj.visit(this);
|
||||
var key = ast.key.visit(this);
|
||||
return this._addRecord(RecordType.KEYED_READ, "keyedAccess", ChangeDetectionUtil.keyedAccess,
|
||||
[key], null, obj);
|
||||
}
|
||||
|
||||
visitChain(ast: Chain): number {
|
||||
var args = ast.expressions.map(e => e.visit(this));
|
||||
return this._addRecord(RecordType.CHAIN, "chain", null, args, null, 0);
|
||||
}
|
||||
|
||||
visitIf(ast: If) { throw new BaseException('Not supported'); }
|
||||
|
||||
_visitAll(asts: List<any>) {
|
||||
var res = ListWrapper.createFixedSize(asts.length);
|
||||
for (var i = 0; i < asts.length; ++i) {
|
||||
res[i] = asts[i].visit(this);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
_addRecord(type, name, funcOrValue, args, fixedArgs, context) {
|
||||
var selfIndex = this._records.length + 1;
|
||||
if (context instanceof DirectiveIndex) {
|
||||
this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, -1, context,
|
||||
selfIndex, this._bindingRecord, false, false, false, false,
|
||||
this._bindingIndex));
|
||||
} else {
|
||||
this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, null,
|
||||
selfIndex, this._bindingRecord, false, false, false, false,
|
||||
this._bindingIndex));
|
||||
}
|
||||
return selfIndex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _arrayFn(length: number): Function {
|
||||
switch (length) {
|
||||
case 0:
|
||||
return ChangeDetectionUtil.arrayFn0;
|
||||
case 1:
|
||||
return ChangeDetectionUtil.arrayFn1;
|
||||
case 2:
|
||||
return ChangeDetectionUtil.arrayFn2;
|
||||
case 3:
|
||||
return ChangeDetectionUtil.arrayFn3;
|
||||
case 4:
|
||||
return ChangeDetectionUtil.arrayFn4;
|
||||
case 5:
|
||||
return ChangeDetectionUtil.arrayFn5;
|
||||
case 6:
|
||||
return ChangeDetectionUtil.arrayFn6;
|
||||
case 7:
|
||||
return ChangeDetectionUtil.arrayFn7;
|
||||
case 8:
|
||||
return ChangeDetectionUtil.arrayFn8;
|
||||
case 9:
|
||||
return ChangeDetectionUtil.arrayFn9;
|
||||
default:
|
||||
throw new BaseException(`Does not support literal maps with more than 9 elements`);
|
||||
}
|
||||
}
|
||||
|
||||
function _mapPrimitiveName(keys: List<any>) {
|
||||
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_identical";
|
||||
case '!==':
|
||||
return "operation_not_identical";
|
||||
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 {
|
||||
switch (operation) {
|
||||
case '+':
|
||||
return ChangeDetectionUtil.operation_add;
|
||||
case '-':
|
||||
return ChangeDetectionUtil.operation_subtract;
|
||||
case '*':
|
||||
return ChangeDetectionUtil.operation_multiply;
|
||||
case '/':
|
||||
return ChangeDetectionUtil.operation_divide;
|
||||
case '%':
|
||||
return ChangeDetectionUtil.operation_remainder;
|
||||
case '==':
|
||||
return ChangeDetectionUtil.operation_equals;
|
||||
case '!=':
|
||||
return ChangeDetectionUtil.operation_not_equals;
|
||||
case '===':
|
||||
return ChangeDetectionUtil.operation_identical;
|
||||
case '!==':
|
||||
return ChangeDetectionUtil.operation_not_identical;
|
||||
case '<':
|
||||
return ChangeDetectionUtil.operation_less_then;
|
||||
case '>':
|
||||
return ChangeDetectionUtil.operation_greater_then;
|
||||
case '<=':
|
||||
return ChangeDetectionUtil.operation_less_or_equals_then;
|
||||
case '>=':
|
||||
return ChangeDetectionUtil.operation_greater_or_equals_then;
|
||||
case '&&':
|
||||
return ChangeDetectionUtil.operation_logical_and;
|
||||
case '||':
|
||||
return ChangeDetectionUtil.operation_logical_or;
|
||||
default:
|
||||
throw new BaseException(`Unsupported operation ${operation}`);
|
||||
}
|
||||
}
|
||||
|
||||
function s(v): string {
|
||||
return isPresent(v) ? `${v}` : '';
|
||||
}
|
||||
|
||||
function _interpolationFn(strings: List<any>) {
|
||||
var length = strings.length;
|
||||
var c0 = length > 0 ? strings[0] : null;
|
||||
var c1 = length > 1 ? strings[1] : null;
|
||||
var c2 = length > 2 ? strings[2] : null;
|
||||
var c3 = length > 3 ? strings[3] : null;
|
||||
var c4 = length > 4 ? strings[4] : null;
|
||||
var c5 = length > 5 ? strings[5] : null;
|
||||
var c6 = length > 6 ? strings[6] : null;
|
||||
var c7 = length > 7 ? strings[7] : null;
|
||||
var c8 = length > 8 ? strings[8] : null;
|
||||
var c9 = length > 9 ? strings[9] : null;
|
||||
switch (length - 1) {
|
||||
case 1:
|
||||
return (a1) => c0 + s(a1) + c1;
|
||||
case 2:
|
||||
return (a1, a2) => c0 + s(a1) + c1 + s(a2) + c2;
|
||||
case 3:
|
||||
return (a1, a2, a3) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3;
|
||||
case 4:
|
||||
return (a1, a2, a3, a4) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4;
|
||||
case 5:
|
||||
return (a1, a2, a3, a4, a5) =>
|
||||
c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5;
|
||||
case 6:
|
||||
return (a1, a2, a3, a4, a5, a6) =>
|
||||
c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6;
|
||||
case 7:
|
||||
return (a1, a2, a3, a4, a5, a6, a7) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) +
|
||||
c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7;
|
||||
case 8:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8) => 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;
|
||||
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`);
|
||||
}
|
||||
}
|
46
modules/angular2/src/core/change_detection/proto_record.ts
Normal file
46
modules/angular2/src/core/change_detection/proto_record.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import {List} from 'angular2/src/facade/collection';
|
||||
import {BindingRecord} from './binding_record';
|
||||
import {DirectiveIndex} from './directive_record';
|
||||
|
||||
export enum RecordType {
|
||||
SELF,
|
||||
CONST,
|
||||
PRIMITIVE_OP,
|
||||
PROPERTY_READ,
|
||||
PROPERTY_WRITE,
|
||||
LOCAL,
|
||||
INVOKE_METHOD,
|
||||
INVOKE_CLOSURE,
|
||||
KEYED_READ,
|
||||
KEYED_WRITE,
|
||||
PIPE,
|
||||
INTERPOLATE,
|
||||
SAFE_PROPERTY,
|
||||
COLLECTION_LITERAL,
|
||||
SAFE_INVOKE_METHOD,
|
||||
DIRECTIVE_LIFECYCLE,
|
||||
CHAIN
|
||||
}
|
||||
|
||||
export class ProtoRecord {
|
||||
constructor(public mode: RecordType, public name: string, public funcOrValue,
|
||||
public args: List<any>, public fixedArgs: List<any>, public contextIndex: number,
|
||||
public directiveIndex: DirectiveIndex, public selfIndex: number,
|
||||
public bindingRecord: BindingRecord, public lastInBinding: boolean,
|
||||
public lastInDirective: boolean, public argumentToPureFunction: boolean,
|
||||
public referencedBySelf: boolean, public propertyBindingIndex: number) {}
|
||||
|
||||
isPureFunction(): boolean {
|
||||
return this.mode === RecordType.INTERPOLATE || this.mode === RecordType.COLLECTION_LITERAL;
|
||||
}
|
||||
|
||||
isUsedByOtherRecord(): boolean { return !this.lastInBinding || this.referencedBySelf; }
|
||||
|
||||
shouldBeChecked(): boolean {
|
||||
return this.argumentToPureFunction || this.lastInBinding || this.isPureFunction();
|
||||
}
|
||||
|
||||
isPipeRecord(): boolean { return this.mode === RecordType.PIPE; }
|
||||
|
||||
isLifeCycleRecord(): boolean { return this.mode === RecordType.DIRECTIVE_LIFECYCLE; }
|
||||
}
|
Reference in New Issue
Block a user