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; }
|
||||
}
|
194
modules/angular2/src/core/debug/debug_element.ts
Normal file
194
modules/angular2/src/core/debug/debug_element.ts
Normal file
@ -0,0 +1,194 @@
|
||||
import {Type, isPresent, BaseException, isBlank} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper, MapWrapper, Predicate} from 'angular2/src/facade/collection';
|
||||
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {ElementInjector} from 'angular2/src/core/compiler/element_injector';
|
||||
import {AppView} from 'angular2/src/core/compiler/view';
|
||||
import {internalView} from 'angular2/src/core/compiler/view_ref';
|
||||
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
|
||||
|
||||
/**
|
||||
* A DebugElement contains information from the Angular compiler about an
|
||||
* element and provides access to the corresponding ElementInjector and
|
||||
* underlying dom Element, as well as a way to query for children.
|
||||
*/
|
||||
export class DebugElement {
|
||||
_elementInjector: ElementInjector;
|
||||
|
||||
constructor(private _parentView: AppView, private _boundElementIndex: number) {
|
||||
this._elementInjector = this._parentView.elementInjectors[this._boundElementIndex];
|
||||
}
|
||||
|
||||
static create(elementRef: ElementRef): DebugElement {
|
||||
return new DebugElement(internalView(elementRef.parentView), elementRef.boundElementIndex);
|
||||
}
|
||||
|
||||
get componentInstance(): any {
|
||||
if (!isPresent(this._elementInjector)) {
|
||||
return null;
|
||||
}
|
||||
return this._elementInjector.getComponent();
|
||||
}
|
||||
|
||||
get nativeElement(): any { return this.elementRef.nativeElement; }
|
||||
|
||||
get elementRef(): ElementRef { return this._parentView.elementRefs[this._boundElementIndex]; }
|
||||
|
||||
getDirectiveInstance(directiveIndex: number): any {
|
||||
return this._elementInjector.getDirectiveAtIndex(directiveIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get child DebugElements from within the Light DOM.
|
||||
*
|
||||
* @return {List<DebugElement>}
|
||||
*/
|
||||
get children(): List<DebugElement> {
|
||||
return this._getChildElements(this._parentView, this._boundElementIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the root DebugElement children of a component. Returns an empty
|
||||
* list if the current DebugElement is not a component root.
|
||||
*
|
||||
* @return {List<DebugElement>}
|
||||
*/
|
||||
get componentViewChildren(): List<DebugElement> {
|
||||
var shadowView = this._parentView.getNestedView(this._boundElementIndex);
|
||||
|
||||
if (!isPresent(shadowView)) {
|
||||
// The current element is not a component.
|
||||
return [];
|
||||
}
|
||||
|
||||
return this._getChildElements(shadowView, null);
|
||||
}
|
||||
|
||||
triggerEventHandler(eventName: string, eventObj: Event): void {
|
||||
this._parentView.triggerEventHandlers(eventName, eventObj, this._boundElementIndex);
|
||||
}
|
||||
|
||||
hasDirective(type: Type): boolean {
|
||||
if (!isPresent(this._elementInjector)) {
|
||||
return false;
|
||||
}
|
||||
return this._elementInjector.hasDirective(type);
|
||||
}
|
||||
|
||||
inject(type: Type): any {
|
||||
if (!isPresent(this._elementInjector)) {
|
||||
return null;
|
||||
}
|
||||
return this._elementInjector.get(type);
|
||||
}
|
||||
|
||||
getLocal(name: string): any { return this._parentView.locals.get(name); }
|
||||
|
||||
/**
|
||||
* Return the first descendant TestElement matching the given predicate
|
||||
* and scope.
|
||||
*
|
||||
* @param {Function: boolean} predicate
|
||||
* @param {Scope} scope
|
||||
*
|
||||
* @return {DebugElement}
|
||||
*/
|
||||
query(predicate: Predicate<DebugElement>, scope: Function = Scope.all): DebugElement {
|
||||
var results = this.queryAll(predicate, scope);
|
||||
return results.length > 0 ? results[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return descendant TestElememts matching the given predicate
|
||||
* and scope.
|
||||
*
|
||||
* @param {Function: boolean} predicate
|
||||
* @param {Scope} scope
|
||||
*
|
||||
* @return {List<DebugElement>}
|
||||
*/
|
||||
queryAll(predicate: Predicate<DebugElement>, scope: Function = Scope.all): List<DebugElement> {
|
||||
var elementsInScope = scope(this);
|
||||
|
||||
return ListWrapper.filter(elementsInScope, predicate);
|
||||
}
|
||||
|
||||
_getChildElements(view: AppView, parentBoundElementIndex: number): List<DebugElement> {
|
||||
var els = [];
|
||||
var parentElementBinder = null;
|
||||
if (isPresent(parentBoundElementIndex)) {
|
||||
parentElementBinder = view.proto.elementBinders[parentBoundElementIndex - view.elementOffset];
|
||||
}
|
||||
for (var i = 0; i < view.proto.elementBinders.length; ++i) {
|
||||
var binder = view.proto.elementBinders[i];
|
||||
if (binder.parent == parentElementBinder) {
|
||||
els.push(new DebugElement(view, view.elementOffset + i));
|
||||
|
||||
var views = view.viewContainers[view.elementOffset + i];
|
||||
if (isPresent(views)) {
|
||||
ListWrapper.forEach(views.views, (nextView) => {
|
||||
els = ListWrapper.concat(els, this._getChildElements(nextView, null));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return els;
|
||||
}
|
||||
}
|
||||
|
||||
export function inspectElement(elementRef: ElementRef): DebugElement {
|
||||
return DebugElement.create(elementRef);
|
||||
}
|
||||
|
||||
export function asNativeElements(arr: List<DebugElement>): List<any> {
|
||||
return arr.map((debugEl) => debugEl.nativeElement);
|
||||
}
|
||||
|
||||
export class Scope {
|
||||
static all(debugElement: DebugElement): List<DebugElement> {
|
||||
var scope = [];
|
||||
scope.push(debugElement);
|
||||
|
||||
ListWrapper.forEach(debugElement.children,
|
||||
(child) => { scope = ListWrapper.concat(scope, Scope.all(child)); });
|
||||
|
||||
ListWrapper.forEach(debugElement.componentViewChildren,
|
||||
(child) => { scope = ListWrapper.concat(scope, Scope.all(child)); });
|
||||
|
||||
return scope;
|
||||
}
|
||||
static light(debugElement: DebugElement): List<DebugElement> {
|
||||
var scope = [];
|
||||
ListWrapper.forEach(debugElement.children, (child) => {
|
||||
scope.push(child);
|
||||
scope = ListWrapper.concat(scope, Scope.light(child));
|
||||
});
|
||||
return scope;
|
||||
}
|
||||
|
||||
static view(debugElement: DebugElement): List<DebugElement> {
|
||||
var scope = [];
|
||||
|
||||
ListWrapper.forEach(debugElement.componentViewChildren, (child) => {
|
||||
scope.push(child);
|
||||
scope = ListWrapper.concat(scope, Scope.light(child));
|
||||
});
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
|
||||
export class By {
|
||||
static all(): Function { return (debugElement) => true; }
|
||||
|
||||
static css(selector: string): Predicate<DebugElement> {
|
||||
return (debugElement) => {
|
||||
return isPresent(debugElement.nativeElement) ?
|
||||
DOM.elementMatches(debugElement.nativeElement, selector) :
|
||||
false;
|
||||
};
|
||||
}
|
||||
static directive(type: Type): Predicate<DebugElement> {
|
||||
return (debugElement) => { return debugElement.hasDirective(type); };
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
import {CONST_EXPR, isPresent, NumberWrapper, StringWrapper} from 'angular2/src/facade/lang';
|
||||
import {MapWrapper, Map, ListWrapper, List} from 'angular2/src/facade/collection';
|
||||
import {Injectable, bind, Binding} from 'angular2/di';
|
||||
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
|
||||
import {AppView} from 'angular2/src/core/compiler/view';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {Renderer} from 'angular2/src/render/api';
|
||||
import {DebugElement} from './debug_element';
|
||||
|
||||
const NG_ID_PROPERTY = 'ngid';
|
||||
const INSPECT_GLOBAL_NAME = 'ngProbe';
|
||||
|
||||
var NG_ID_SEPARATOR = '#';
|
||||
|
||||
// Need to keep the views in a global Map so that multiple angular apps are supported
|
||||
var _allIdsByView = new Map<AppView, number>();
|
||||
var _allViewsById = new Map<number, AppView>();
|
||||
|
||||
var _nextId = 0;
|
||||
|
||||
function _setElementId(element, indices: List<number>) {
|
||||
if (isPresent(element)) {
|
||||
DOM.setData(element, NG_ID_PROPERTY, ListWrapper.join(indices, NG_ID_SEPARATOR));
|
||||
}
|
||||
}
|
||||
|
||||
function _getElementId(element): List<number> {
|
||||
var elId = DOM.getData(element, NG_ID_PROPERTY);
|
||||
if (isPresent(elId)) {
|
||||
return ListWrapper.map(elId.split(NG_ID_SEPARATOR),
|
||||
(partStr) => NumberWrapper.parseInt(partStr, 10));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function inspectNativeElement(element): DebugElement {
|
||||
var elId = _getElementId(element);
|
||||
if (isPresent(elId)) {
|
||||
var view = _allViewsById.get(elId[0]);
|
||||
if (isPresent(view)) {
|
||||
return new DebugElement(view, elId[1]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DebugElementViewListener implements AppViewListener {
|
||||
constructor(private _renderer: Renderer) {
|
||||
DOM.setGlobalVar(INSPECT_GLOBAL_NAME, inspectNativeElement);
|
||||
}
|
||||
|
||||
viewCreated(view: AppView) {
|
||||
var viewId = _nextId++;
|
||||
_allViewsById.set(viewId, view);
|
||||
_allIdsByView.set(view, viewId);
|
||||
for (var i = 0; i < view.elementRefs.length; i++) {
|
||||
var el = view.elementRefs[i];
|
||||
_setElementId(this._renderer.getNativeElementSync(el), [viewId, i]);
|
||||
}
|
||||
}
|
||||
|
||||
viewDestroyed(view: AppView) {
|
||||
var viewId = _allIdsByView.get(view);
|
||||
MapWrapper.delete(_allIdsByView, view);
|
||||
MapWrapper.delete(_allViewsById, viewId);
|
||||
}
|
||||
}
|
||||
|
||||
export const ELEMENT_PROBE_BINDINGS = CONST_EXPR([
|
||||
DebugElementViewListener,
|
||||
CONST_EXPR(new Binding(AppViewListener, {toAlias: DebugElementViewListener})),
|
||||
]);
|
447
modules/angular2/src/core/di/binding.ts
Normal file
447
modules/angular2/src/core/di/binding.ts
Normal file
@ -0,0 +1,447 @@
|
||||
import {
|
||||
Type,
|
||||
isBlank,
|
||||
isPresent,
|
||||
CONST,
|
||||
CONST_EXPR,
|
||||
BaseException,
|
||||
stringify,
|
||||
isArray
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
import {Key} from './key';
|
||||
import {
|
||||
InjectMetadata,
|
||||
InjectableMetadata,
|
||||
OptionalMetadata,
|
||||
SelfMetadata,
|
||||
HostMetadata,
|
||||
SkipSelfMetadata,
|
||||
DependencyMetadata
|
||||
} from './metadata';
|
||||
import {NoAnnotationError} from './exceptions';
|
||||
import {resolveForwardRef} from './forward_ref';
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export class Dependency {
|
||||
constructor(public key: Key, public optional: boolean, public lowerBoundVisibility: any,
|
||||
public upperBoundVisibility: any, public properties: List<any>) {}
|
||||
|
||||
static fromKey(key: Key): Dependency { return new Dependency(key, false, null, null, []); }
|
||||
}
|
||||
|
||||
const _EMPTY_LIST = CONST_EXPR([]);
|
||||
|
||||
/**
|
||||
* Describes how_ the {@link Injector} should instantiate a given token.
|
||||
*
|
||||
* See {@link bind}.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```javascript
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* new Binding(String, { toValue: 'Hello' })
|
||||
* ]);
|
||||
*
|
||||
* expect(injector.get(String)).toEqual('Hello');
|
||||
* ```
|
||||
*/
|
||||
@CONST()
|
||||
export class Binding {
|
||||
/**
|
||||
* Token used when retrieving this binding. Usually the `Type`.
|
||||
*/
|
||||
token;
|
||||
|
||||
/**
|
||||
* Binds an interface to an implementation / subclass.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* Becuse `toAlias` and `toClass` are often confused, the example contains both use cases for easy
|
||||
* comparison.
|
||||
*
|
||||
* ```javascript
|
||||
*
|
||||
* class Vehicle {}
|
||||
*
|
||||
* class Car extends Vehicle {}
|
||||
*
|
||||
* var injectorClass = Injector.resolveAndCreate([
|
||||
* Car,
|
||||
* new Binding(Vehicle, { toClass: Car })
|
||||
* ]);
|
||||
* var injectorAlias = Injector.resolveAndCreate([
|
||||
* Car,
|
||||
* new Binding(Vehicle, { toAlias: Car })
|
||||
* ]);
|
||||
*
|
||||
* expect(injectorClass.get(Vehicle)).not.toBe(injectorClass.get(Car));
|
||||
* expect(injectorClass.get(Vehicle) instanceof Car).toBe(true);
|
||||
*
|
||||
* expect(injectorAlias.get(Vehicle)).toBe(injectorAlias.get(Car));
|
||||
* expect(injectorAlias.get(Vehicle) instanceof Car).toBe(true);
|
||||
* ```
|
||||
*/
|
||||
toClass: Type;
|
||||
|
||||
/**
|
||||
* Binds a key to a value.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```javascript
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* new Binding(String, { toValue: 'Hello' })
|
||||
* ]);
|
||||
*
|
||||
* expect(injector.get(String)).toEqual('Hello');
|
||||
* ```
|
||||
*/
|
||||
toValue;
|
||||
|
||||
/**
|
||||
* Binds a key to the alias for an existing key.
|
||||
*
|
||||
* An alias means that {@link Injector} returns the same instance as if the alias token was used.
|
||||
* This is in contrast to `toClass` where a separate instance of `toClass` is returned.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* Becuse `toAlias` and `toClass` are often confused the example contains both use cases for easy
|
||||
* comparison.
|
||||
*
|
||||
* ```javascript
|
||||
*
|
||||
* class Vehicle {}
|
||||
*
|
||||
* class Car extends Vehicle {}
|
||||
*
|
||||
* var injectorAlias = Injector.resolveAndCreate([
|
||||
* Car,
|
||||
* new Binding(Vehicle, { toAlias: Car })
|
||||
* ]);
|
||||
* var injectorClass = Injector.resolveAndCreate([
|
||||
* Car,
|
||||
* new Binding(Vehicle, { toClass: Car })
|
||||
* ]);
|
||||
*
|
||||
* expect(injectorAlias.get(Vehicle)).toBe(injectorAlias.get(Car));
|
||||
* expect(injectorAlias.get(Vehicle) instanceof Car).toBe(true);
|
||||
*
|
||||
* expect(injectorClass.get(Vehicle)).not.toBe(injectorClass.get(Car));
|
||||
* expect(injectorClass.get(Vehicle) instanceof Car).toBe(true);
|
||||
* ```
|
||||
*/
|
||||
toAlias;
|
||||
|
||||
/**
|
||||
* Binds a key to a function which computes the value.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```javascript
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* new Binding(Number, { toFactory: () => { return 1+2; }}),
|
||||
* new Binding(String, { toFactory: (value) => { return "Value: " + value; },
|
||||
* dependencies: [Number] })
|
||||
* ]);
|
||||
*
|
||||
* expect(injector.get(Number)).toEqual(3);
|
||||
* expect(injector.get(String)).toEqual('Value: 3');
|
||||
* ```
|
||||
*/
|
||||
toFactory: Function;
|
||||
|
||||
/**
|
||||
* Used in conjunction with `toFactory` and specifies a set of dependencies
|
||||
* (as `token`s) which should be injected into the factory function.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```javascript
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* new Binding(Number, { toFactory: () => { return 1+2; }}),
|
||||
* new Binding(String, { toFactory: (value) => { return "Value: " + value; },
|
||||
* dependencies: [Number] })
|
||||
* ]);
|
||||
*
|
||||
* expect(injector.get(Number)).toEqual(3);
|
||||
* expect(injector.get(String)).toEqual('Value: 3');
|
||||
* ```
|
||||
*/
|
||||
dependencies: List<any>;
|
||||
|
||||
constructor(
|
||||
token,
|
||||
{toClass, toValue, toAlias, toFactory, deps}:
|
||||
{toClass?: Type, toValue?: any, toAlias?: any, toFactory?: Function, deps?: List<any>}) {
|
||||
this.token = token;
|
||||
this.toClass = toClass;
|
||||
this.toValue = toValue;
|
||||
this.toAlias = toAlias;
|
||||
this.toFactory = toFactory;
|
||||
this.dependencies = deps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@link Binding} into {@link ResolvedBinding}.
|
||||
*
|
||||
* {@link Injector} internally only uses {@link ResolvedBinding}, {@link Binding} contains
|
||||
* convenience binding syntax.
|
||||
*/
|
||||
resolve(): ResolvedBinding {
|
||||
var factoryFn: Function;
|
||||
var resolvedDeps;
|
||||
if (isPresent(this.toClass)) {
|
||||
var toClass = resolveForwardRef(this.toClass);
|
||||
factoryFn = reflector.factory(toClass);
|
||||
resolvedDeps = _dependenciesFor(toClass);
|
||||
} else if (isPresent(this.toAlias)) {
|
||||
factoryFn = (aliasInstance) => aliasInstance;
|
||||
resolvedDeps = [Dependency.fromKey(Key.get(this.toAlias))];
|
||||
} else if (isPresent(this.toFactory)) {
|
||||
factoryFn = this.toFactory;
|
||||
resolvedDeps = _constructDependencies(this.toFactory, this.dependencies);
|
||||
} else {
|
||||
factoryFn = () => this.toValue;
|
||||
resolvedDeps = _EMPTY_LIST;
|
||||
}
|
||||
|
||||
return new ResolvedBinding(Key.get(this.token), factoryFn, resolvedDeps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An internal resolved representation of a {@link Binding} used by the {@link Injector}.
|
||||
*
|
||||
* A {@link Binding} is resolved when it has a factory function. Binding to a class, alias, or
|
||||
* value, are just convenience methods, as {@link Injector} only operates on calling factory
|
||||
* functions.
|
||||
*/
|
||||
export class ResolvedBinding {
|
||||
constructor(
|
||||
/**
|
||||
* A key, usually a `Type`.
|
||||
*/
|
||||
public key: Key,
|
||||
|
||||
/**
|
||||
* Factory function which can return an instance of an object represented by a key.
|
||||
*/
|
||||
public factory: Function,
|
||||
|
||||
/**
|
||||
* Arguments (dependencies) to the `factory` function.
|
||||
*/
|
||||
public dependencies: List<Dependency>) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an API for imperatively constructing {@link Binding}s.
|
||||
*
|
||||
* This is only relevant for JavaScript. See {@link BindingBuilder}.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```javascript
|
||||
* bind(MyInterface).toClass(MyClass)
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
export function bind(token): BindingBuilder {
|
||||
return new BindingBuilder(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class for the {@link bind} function.
|
||||
*/
|
||||
export class BindingBuilder {
|
||||
constructor(public token) {}
|
||||
|
||||
/**
|
||||
* Binds an interface to an implementation / subclass.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* Because `toAlias` and `toClass` are often confused, the example contains both use cases for
|
||||
* easy comparison.
|
||||
*
|
||||
* ```javascript
|
||||
*
|
||||
* class Vehicle {}
|
||||
*
|
||||
* class Car extends Vehicle {}
|
||||
*
|
||||
* var injectorClass = Injector.resolveAndCreate([
|
||||
* Car,
|
||||
* bind(Vehicle).toClass(Car)
|
||||
* ]);
|
||||
* var injectorAlias = Injector.resolveAndCreate([
|
||||
* Car,
|
||||
* bind(Vehicle).toAlias(Car)
|
||||
* ]);
|
||||
*
|
||||
* expect(injectorClass.get(Vehicle)).not.toBe(injectorClass.get(Car));
|
||||
* expect(injectorClass.get(Vehicle) instanceof Car).toBe(true);
|
||||
*
|
||||
* expect(injectorAlias.get(Vehicle)).toBe(injectorAlias.get(Car));
|
||||
* expect(injectorAlias.get(Vehicle) instanceof Car).toBe(true);
|
||||
* ```
|
||||
*/
|
||||
toClass(type: Type): Binding { return new Binding(this.token, {toClass: type}); }
|
||||
|
||||
/**
|
||||
* Binds a key to a value.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```javascript
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* bind(String).toValue('Hello')
|
||||
* ]);
|
||||
*
|
||||
* expect(injector.get(String)).toEqual('Hello');
|
||||
* ```
|
||||
*/
|
||||
toValue(value: any): Binding { return new Binding(this.token, {toValue: value}); }
|
||||
|
||||
/**
|
||||
* Binds a key to the alias for an existing key.
|
||||
*
|
||||
* An alias means that we will return the same instance as if the alias token was used. (This is
|
||||
* in contrast to `toClass` where a separate instance of `toClass` will be returned.)
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* Becuse `toAlias` and `toClass` are often confused, the example contains both use cases for easy
|
||||
* comparison.
|
||||
*
|
||||
* ```javascript
|
||||
*
|
||||
* class Vehicle {}
|
||||
*
|
||||
* class Car extends Vehicle {}
|
||||
*
|
||||
* var injectorAlias = Injector.resolveAndCreate([
|
||||
* Car,
|
||||
* bind(Vehicle).toAlias(Car)
|
||||
* ]);
|
||||
* var injectorClass = Injector.resolveAndCreate([
|
||||
* Car,
|
||||
* bind(Vehicle).toClass(Car)
|
||||
* ]);
|
||||
*
|
||||
* expect(injectorAlias.get(Vehicle)).toBe(injectorAlias.get(Car));
|
||||
* expect(injectorAlias.get(Vehicle) instanceof Car).toBe(true);
|
||||
*
|
||||
* expect(injectorClass.get(Vehicle)).not.toBe(injectorClass.get(Car));
|
||||
* expect(injectorClass.get(Vehicle) instanceof Car).toBe(true);
|
||||
* ```
|
||||
*/
|
||||
toAlias(aliasToken: /*Type*/ any): Binding {
|
||||
if (isBlank(aliasToken)) {
|
||||
throw new BaseException(`Can not alias ${stringify(this.token)} to a blank value!`);
|
||||
}
|
||||
return new Binding(this.token, {toAlias: aliasToken});
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a key to a function which computes the value.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```javascript
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* bind(Number).toFactory(() => { return 1+2; }),
|
||||
* bind(String).toFactory((v) => { return "Value: " + v; }, [Number])
|
||||
* ]);
|
||||
*
|
||||
* expect(injector.get(Number)).toEqual(3);
|
||||
* expect(injector.get(String)).toEqual('Value: 3');
|
||||
* ```
|
||||
*/
|
||||
toFactory(factoryFunction: Function, dependencies?: List<any>): Binding {
|
||||
return new Binding(this.token, {toFactory: factoryFunction, deps: dependencies});
|
||||
}
|
||||
}
|
||||
|
||||
function _constructDependencies(factoryFunction: Function, dependencies: List<any>):
|
||||
List<Dependency> {
|
||||
if (isBlank(dependencies)) {
|
||||
return _dependenciesFor(factoryFunction);
|
||||
} else {
|
||||
var params: List<List<any>> = ListWrapper.map(dependencies, (t) => [t]);
|
||||
return ListWrapper.map(dependencies, (t) => _extractToken(factoryFunction, t, params));
|
||||
}
|
||||
}
|
||||
|
||||
function _dependenciesFor(typeOrFunc): List<Dependency> {
|
||||
var params = reflector.parameters(typeOrFunc);
|
||||
if (isBlank(params)) return [];
|
||||
if (ListWrapper.any(params, (p) => isBlank(p))) {
|
||||
throw new NoAnnotationError(typeOrFunc, params);
|
||||
}
|
||||
return ListWrapper.map(params, (p: List<any>) => _extractToken(typeOrFunc, p, params));
|
||||
}
|
||||
|
||||
function _extractToken(typeOrFunc, metadata /*List<any> | any*/, params: List<List<any>>):
|
||||
Dependency {
|
||||
var depProps = [];
|
||||
var token = null;
|
||||
var optional = false;
|
||||
|
||||
if (!isArray(metadata)) {
|
||||
return _createDependency(metadata, optional, null, null, depProps);
|
||||
}
|
||||
|
||||
var lowerBoundVisibility = null;
|
||||
var upperBoundVisibility = null;
|
||||
|
||||
for (var i = 0; i < metadata.length; ++i) {
|
||||
var paramMetadata = metadata[i];
|
||||
|
||||
if (paramMetadata instanceof Type) {
|
||||
token = paramMetadata;
|
||||
|
||||
} else if (paramMetadata instanceof InjectMetadata) {
|
||||
token = paramMetadata.token;
|
||||
|
||||
} else if (paramMetadata instanceof OptionalMetadata) {
|
||||
optional = true;
|
||||
|
||||
} else if (paramMetadata instanceof SelfMetadata) {
|
||||
upperBoundVisibility = paramMetadata;
|
||||
|
||||
} else if (paramMetadata instanceof HostMetadata) {
|
||||
upperBoundVisibility = paramMetadata;
|
||||
|
||||
} else if (paramMetadata instanceof SkipSelfMetadata) {
|
||||
lowerBoundVisibility = paramMetadata;
|
||||
|
||||
} else if (paramMetadata instanceof DependencyMetadata) {
|
||||
if (isPresent(paramMetadata.token)) {
|
||||
token = paramMetadata.token;
|
||||
}
|
||||
depProps.push(paramMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
token = resolveForwardRef(token);
|
||||
|
||||
if (isPresent(token)) {
|
||||
return _createDependency(token, optional, lowerBoundVisibility, upperBoundVisibility, depProps);
|
||||
} else {
|
||||
throw new NoAnnotationError(typeOrFunc, params);
|
||||
}
|
||||
}
|
||||
|
||||
function _createDependency(token, optional, lowerBoundVisibility, upperBoundVisibility, depProps):
|
||||
Dependency {
|
||||
return new Dependency(Key.get(token), optional, lowerBoundVisibility, upperBoundVisibility,
|
||||
depProps);
|
||||
}
|
46
modules/angular2/src/core/di/decorators.dart
Normal file
46
modules/angular2/src/core/di/decorators.dart
Normal file
@ -0,0 +1,46 @@
|
||||
library angular2.di.decorators;
|
||||
|
||||
import 'metadata.dart';
|
||||
export 'metadata.dart';
|
||||
|
||||
/**
|
||||
* {@link InjectMetadata}.
|
||||
*/
|
||||
class Inject extends InjectMetadata {
|
||||
const Inject(dynamic token) : super(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link OptionalMetadata}.
|
||||
*/
|
||||
class Optional extends OptionalMetadata {
|
||||
const Optional() : super();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link InjectableMetadata}.
|
||||
*/
|
||||
class Injectable extends InjectableMetadata {
|
||||
const Injectable() : super();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SelfMetadata}.
|
||||
*/
|
||||
class Self extends SelfMetadata {
|
||||
const Self() : super();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link HostMetadata}.
|
||||
*/
|
||||
class Host extends HostMetadata {
|
||||
const Host() : super();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SkipSelfMetadata}.
|
||||
*/
|
||||
class SkipSelf extends SkipSelfMetadata {
|
||||
const SkipSelf() : super();
|
||||
}
|
87
modules/angular2/src/core/di/decorators.ts
Normal file
87
modules/angular2/src/core/di/decorators.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import {
|
||||
InjectMetadata,
|
||||
OptionalMetadata,
|
||||
InjectableMetadata,
|
||||
SelfMetadata,
|
||||
HostMetadata,
|
||||
SkipSelfMetadata
|
||||
} from './metadata';
|
||||
import {makeDecorator, makeParamDecorator, TypeDecorator} from '../util/decorators';
|
||||
|
||||
/**
|
||||
* Factory for creating {@link InjectMetadata}.
|
||||
*/
|
||||
export interface InjectFactory {
|
||||
(token: any): any;
|
||||
new (token: any): InjectMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for creating {@link OptionalMetadata}.
|
||||
*/
|
||||
export interface OptionalFactory {
|
||||
(): any;
|
||||
new (): OptionalMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for creating {@link InjectableMetadata}.
|
||||
*/
|
||||
export interface InjectableFactory {
|
||||
(): any;
|
||||
new (): InjectableMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for creating {@link SelfMetadata}.
|
||||
*/
|
||||
export interface SelfFactory {
|
||||
(): any;
|
||||
new (): SelfMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for creating {@link HostMetadata}.
|
||||
*/
|
||||
export interface HostFactory {
|
||||
(): any;
|
||||
new (): HostMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for creating {@link SkipSelfMetadata}.
|
||||
*/
|
||||
export interface SkipSelfFactory {
|
||||
(): any;
|
||||
new (): SkipSelfMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for creating {@link InjectMetadata}.
|
||||
*/
|
||||
export var Inject: InjectFactory = makeParamDecorator(InjectMetadata);
|
||||
|
||||
/**
|
||||
* Factory for creating {@link OptionalMetadata}.
|
||||
*/
|
||||
export var Optional: OptionalFactory = makeParamDecorator(OptionalMetadata);
|
||||
|
||||
/**
|
||||
* Factory for creating {@link InjectableMetadata}.
|
||||
*/
|
||||
export var Injectable: InjectableFactory = <InjectableFactory>makeDecorator(InjectableMetadata);
|
||||
|
||||
/**
|
||||
* Factory for creating {@link SelfMetadata}.
|
||||
*/
|
||||
export var Self: SelfFactory = makeParamDecorator(SelfMetadata);
|
||||
|
||||
/**
|
||||
* Factory for creating {@link HostMetadata}.
|
||||
*/
|
||||
export var Host: HostFactory = makeParamDecorator(HostMetadata);
|
||||
|
||||
/**
|
||||
* Factory for creating {@link SkipSelfMetadata}.
|
||||
*/
|
||||
export var SkipSelf: SkipSelfFactory = makeParamDecorator(SkipSelfMetadata);
|
169
modules/angular2/src/core/di/exceptions.ts
Normal file
169
modules/angular2/src/core/di/exceptions.ts
Normal file
@ -0,0 +1,169 @@
|
||||
import {ListWrapper, List} from 'angular2/src/facade/collection';
|
||||
import {stringify, BaseException, isBlank} from 'angular2/src/facade/lang';
|
||||
import {Key} from './key';
|
||||
import {Injector} from './injector';
|
||||
|
||||
function findFirstClosedCycle(keys: List<any>): List<any> {
|
||||
var res = [];
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
if (ListWrapper.contains(res, keys[i])) {
|
||||
res.push(keys[i]);
|
||||
return res;
|
||||
} else {
|
||||
res.push(keys[i]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function constructResolvingPath(keys: List<any>): string {
|
||||
if (keys.length > 1) {
|
||||
var reversed = findFirstClosedCycle(ListWrapper.reversed(keys));
|
||||
var tokenStrs = ListWrapper.map(reversed, (k) => stringify(k.token));
|
||||
return " (" + tokenStrs.join(' -> ') + ")";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Base class for all errors arising from misconfigured bindings.
|
||||
*/
|
||||
export class AbstractBindingError extends BaseException {
|
||||
name: string;
|
||||
message: string;
|
||||
keys: List<Key>;
|
||||
injectors: List<Injector>;
|
||||
constructResolvingMessage: Function;
|
||||
|
||||
constructor(injector: Injector, key: Key, constructResolvingMessage: Function, originalException?,
|
||||
originalStack?) {
|
||||
super("DI Exception", originalException, originalStack, null);
|
||||
this.keys = [key];
|
||||
this.injectors = [injector];
|
||||
this.constructResolvingMessage = constructResolvingMessage;
|
||||
this.message = this.constructResolvingMessage(this.keys);
|
||||
}
|
||||
|
||||
addKey(injector: Injector, key: Key): void {
|
||||
this.injectors.push(injector);
|
||||
this.keys.push(key);
|
||||
this.message = this.constructResolvingMessage(this.keys);
|
||||
}
|
||||
|
||||
get context() { return this.injectors[this.injectors.length - 1].debugContext(); }
|
||||
|
||||
toString(): string { return this.message; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when trying to retrieve a dependency by `Key` from {@link Injector}, but the
|
||||
* {@link Injector} does not have a {@link Binding} for {@link Key}.
|
||||
*/
|
||||
export class NoBindingError extends AbstractBindingError {
|
||||
constructor(injector: Injector, key: Key) {
|
||||
super(injector, key, function(keys: List<any>) {
|
||||
var first = stringify(ListWrapper.first(keys).token);
|
||||
return `No provider for ${first}!${constructResolvingPath(keys)}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when dependencies form a cycle.
|
||||
*
|
||||
* ## Example:
|
||||
*
|
||||
* ```javascript
|
||||
* class A {
|
||||
* constructor(b:B) {}
|
||||
* }
|
||||
* class B {
|
||||
* constructor(a:A) {}
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Retrieving `A` or `B` throws a `CyclicDependencyError` as the graph above cannot be constructed.
|
||||
*/
|
||||
export class CyclicDependencyError extends AbstractBindingError {
|
||||
constructor(injector: Injector, key: Key) {
|
||||
super(injector, key, function(keys: List<any>) {
|
||||
return `Cannot instantiate cyclic dependency!${constructResolvingPath(keys)}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when a constructing type returns with an Error.
|
||||
*
|
||||
* The `InstantiationError` class contains the original error plus the dependency graph which caused
|
||||
* this object to be instantiated.
|
||||
*/
|
||||
export class InstantiationError extends AbstractBindingError {
|
||||
causeKey: Key;
|
||||
constructor(injector: Injector, originalException, originalStack, key: Key) {
|
||||
super(injector, key, function(keys: List<any>) {
|
||||
var first = stringify(ListWrapper.first(keys).token);
|
||||
return `Error during instantiation of ${first}!${constructResolvingPath(keys)}.`;
|
||||
}, originalException, originalStack);
|
||||
|
||||
this.causeKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when an object other then {@link Binding} (or `Type`) is passed to {@link Injector}
|
||||
* creation.
|
||||
*/
|
||||
export class InvalidBindingError extends BaseException {
|
||||
message: string;
|
||||
constructor(binding) {
|
||||
super();
|
||||
this.message = "Invalid binding - only instances of Binding and Type are allowed, got: " +
|
||||
binding.toString();
|
||||
}
|
||||
|
||||
toString(): string { return this.message; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when the class has no annotation information.
|
||||
*
|
||||
* Lack of annotation information prevents the {@link Injector} from determining which dependencies
|
||||
* need to be injected into the constructor.
|
||||
*/
|
||||
export class NoAnnotationError extends BaseException {
|
||||
name: string;
|
||||
message: string;
|
||||
constructor(typeOrFunc, params: List<List<any>>) {
|
||||
super();
|
||||
var signature = [];
|
||||
for (var i = 0, ii = params.length; i < ii; i++) {
|
||||
var parameter = params[i];
|
||||
if (isBlank(parameter) || parameter.length == 0) {
|
||||
signature.push('?');
|
||||
} else {
|
||||
signature.push(ListWrapper.map(parameter, stringify).join(' '));
|
||||
}
|
||||
}
|
||||
this.message = "Cannot resolve all parameters for " + stringify(typeOrFunc) + "(" +
|
||||
signature.join(', ') + "). " +
|
||||
'Make sure they all have valid type or annotations.';
|
||||
}
|
||||
|
||||
toString(): string { return this.message; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when getting an object by index.
|
||||
*/
|
||||
export class OutOfBoundsError extends BaseException {
|
||||
message: string;
|
||||
constructor(index) {
|
||||
super();
|
||||
this.message = `Index ${index} is out-of-bounds.`;
|
||||
}
|
||||
|
||||
toString(): string { return this.message; }
|
||||
}
|
15
modules/angular2/src/core/di/forward_ref.dart
Normal file
15
modules/angular2/src/core/di/forward_ref.dart
Normal file
@ -0,0 +1,15 @@
|
||||
library angular2.di.forward_ref;
|
||||
|
||||
typedef dynamic ForwardRefFn();
|
||||
|
||||
/**
|
||||
* Dart does not have the forward ref problem, so this function is a noop.
|
||||
*/
|
||||
forwardRef(ForwardRefFn forwardRefFn) => forwardRefFn();
|
||||
|
||||
/**
|
||||
* Lazily retrieve the reference value.
|
||||
*
|
||||
* See: {@link forwardRef}
|
||||
*/
|
||||
resolveForwardRef(type) => type;
|
47
modules/angular2/src/core/di/forward_ref.ts
Normal file
47
modules/angular2/src/core/di/forward_ref.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import {Type, stringify, isFunction} from 'angular2/src/facade/lang';
|
||||
|
||||
export interface ForwardRefFn { (): any; }
|
||||
|
||||
/**
|
||||
* Allows to refer to references which are not yet defined.
|
||||
*
|
||||
* This situation arises when the key which we need te refer to for the purposes of DI is declared,
|
||||
* but not yet defined.
|
||||
*
|
||||
* ## Example:
|
||||
*
|
||||
* ```
|
||||
* class Door {
|
||||
* // Incorrect way to refer to a reference which is defined later.
|
||||
* // This fails because `Lock` is undefined at this point.
|
||||
* constructor(lock:Lock) { }
|
||||
*
|
||||
* // Correct way to refer to a reference which is defined later.
|
||||
* // The reference needs to be captured in a closure.
|
||||
* constructor(@Inject(forwardRef(() => Lock)) lock:Lock) { }
|
||||
* }
|
||||
*
|
||||
* // Only at this point the lock is defined.
|
||||
* class Lock {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function forwardRef(forwardRefFn: ForwardRefFn): Type {
|
||||
(<any>forwardRefFn).__forward_ref__ = forwardRef;
|
||||
(<any>forwardRefFn).toString = function() { return stringify(this()); };
|
||||
return (<Type><any>forwardRefFn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily retrieve the reference value.
|
||||
*
|
||||
* See: {@link forwardRef}
|
||||
*/
|
||||
export function resolveForwardRef(type: any): any {
|
||||
if (isFunction(type) && type.hasOwnProperty('__forward_ref__') &&
|
||||
type.__forward_ref__ === forwardRef) {
|
||||
return (<ForwardRefFn>type)();
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
}
|
850
modules/angular2/src/core/di/injector.ts
Normal file
850
modules/angular2/src/core/di/injector.ts
Normal file
@ -0,0 +1,850 @@
|
||||
import {Map, List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {ResolvedBinding, Binding, Dependency, BindingBuilder, bind} from './binding';
|
||||
import {
|
||||
AbstractBindingError,
|
||||
NoBindingError,
|
||||
CyclicDependencyError,
|
||||
InstantiationError,
|
||||
InvalidBindingError,
|
||||
OutOfBoundsError
|
||||
} from './exceptions';
|
||||
import {FunctionWrapper, Type, isPresent, isBlank, CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
import {Key} from './key';
|
||||
import {resolveForwardRef} from './forward_ref';
|
||||
import {SelfMetadata, HostMetadata, SkipSelfMetadata} from './metadata';
|
||||
|
||||
// Threshold for the dynamic version
|
||||
const _MAX_CONSTRUCTION_COUNTER = 10;
|
||||
|
||||
export const UNDEFINED: Object = CONST_EXPR(new Object());
|
||||
|
||||
export enum Visibility {
|
||||
Public,
|
||||
Private,
|
||||
PublicAndPrivate
|
||||
}
|
||||
|
||||
function canSee(src: Visibility, dst: Visibility): boolean {
|
||||
return (src === dst) ||
|
||||
(dst === Visibility.PublicAndPrivate || src === Visibility.PublicAndPrivate);
|
||||
}
|
||||
|
||||
|
||||
export interface ProtoInjectorStrategy {
|
||||
getBindingAtIndex(index: number): ResolvedBinding;
|
||||
createInjectorStrategy(inj: Injector): InjectorStrategy;
|
||||
}
|
||||
|
||||
export class ProtoInjectorInlineStrategy implements ProtoInjectorStrategy {
|
||||
binding0: ResolvedBinding = null;
|
||||
binding1: ResolvedBinding = null;
|
||||
binding2: ResolvedBinding = null;
|
||||
binding3: ResolvedBinding = null;
|
||||
binding4: ResolvedBinding = null;
|
||||
binding5: ResolvedBinding = null;
|
||||
binding6: ResolvedBinding = null;
|
||||
binding7: ResolvedBinding = null;
|
||||
binding8: ResolvedBinding = null;
|
||||
binding9: ResolvedBinding = null;
|
||||
|
||||
keyId0: number = null;
|
||||
keyId1: number = null;
|
||||
keyId2: number = null;
|
||||
keyId3: number = null;
|
||||
keyId4: number = null;
|
||||
keyId5: number = null;
|
||||
keyId6: number = null;
|
||||
keyId7: number = null;
|
||||
keyId8: number = null;
|
||||
keyId9: number = null;
|
||||
|
||||
visibility0: Visibility = null;
|
||||
visibility1: Visibility = null;
|
||||
visibility2: Visibility = null;
|
||||
visibility3: Visibility = null;
|
||||
visibility4: Visibility = null;
|
||||
visibility5: Visibility = null;
|
||||
visibility6: Visibility = null;
|
||||
visibility7: Visibility = null;
|
||||
visibility8: Visibility = null;
|
||||
visibility9: Visibility = null;
|
||||
|
||||
constructor(protoEI: ProtoInjector, bwv: BindingWithVisibility[]) {
|
||||
var length = bwv.length;
|
||||
|
||||
if (length > 0) {
|
||||
this.binding0 = bwv[0].binding;
|
||||
this.keyId0 = bwv[0].getKeyId();
|
||||
this.visibility0 = bwv[0].visibility;
|
||||
}
|
||||
if (length > 1) {
|
||||
this.binding1 = bwv[1].binding;
|
||||
this.keyId1 = bwv[1].getKeyId();
|
||||
this.visibility1 = bwv[1].visibility;
|
||||
}
|
||||
if (length > 2) {
|
||||
this.binding2 = bwv[2].binding;
|
||||
this.keyId2 = bwv[2].getKeyId();
|
||||
this.visibility2 = bwv[2].visibility;
|
||||
}
|
||||
if (length > 3) {
|
||||
this.binding3 = bwv[3].binding;
|
||||
this.keyId3 = bwv[3].getKeyId();
|
||||
this.visibility3 = bwv[3].visibility;
|
||||
}
|
||||
if (length > 4) {
|
||||
this.binding4 = bwv[4].binding;
|
||||
this.keyId4 = bwv[4].getKeyId();
|
||||
this.visibility4 = bwv[4].visibility;
|
||||
}
|
||||
if (length > 5) {
|
||||
this.binding5 = bwv[5].binding;
|
||||
this.keyId5 = bwv[5].getKeyId();
|
||||
this.visibility5 = bwv[5].visibility;
|
||||
}
|
||||
if (length > 6) {
|
||||
this.binding6 = bwv[6].binding;
|
||||
this.keyId6 = bwv[6].getKeyId();
|
||||
this.visibility6 = bwv[6].visibility;
|
||||
}
|
||||
if (length > 7) {
|
||||
this.binding7 = bwv[7].binding;
|
||||
this.keyId7 = bwv[7].getKeyId();
|
||||
this.visibility7 = bwv[7].visibility;
|
||||
}
|
||||
if (length > 8) {
|
||||
this.binding8 = bwv[8].binding;
|
||||
this.keyId8 = bwv[8].getKeyId();
|
||||
this.visibility8 = bwv[8].visibility;
|
||||
}
|
||||
if (length > 9) {
|
||||
this.binding9 = bwv[9].binding;
|
||||
this.keyId9 = bwv[9].getKeyId();
|
||||
this.visibility9 = bwv[9].visibility;
|
||||
}
|
||||
}
|
||||
|
||||
getBindingAtIndex(index: number): any {
|
||||
if (index == 0) return this.binding0;
|
||||
if (index == 1) return this.binding1;
|
||||
if (index == 2) return this.binding2;
|
||||
if (index == 3) return this.binding3;
|
||||
if (index == 4) return this.binding4;
|
||||
if (index == 5) return this.binding5;
|
||||
if (index == 6) return this.binding6;
|
||||
if (index == 7) return this.binding7;
|
||||
if (index == 8) return this.binding8;
|
||||
if (index == 9) return this.binding9;
|
||||
throw new OutOfBoundsError(index);
|
||||
}
|
||||
|
||||
createInjectorStrategy(injector: Injector): InjectorStrategy {
|
||||
return new InjectorInlineStrategy(injector, this);
|
||||
}
|
||||
}
|
||||
|
||||
export class ProtoInjectorDynamicStrategy implements ProtoInjectorStrategy {
|
||||
bindings: ResolvedBinding[];
|
||||
keyIds: number[];
|
||||
visibilities: Visibility[];
|
||||
|
||||
constructor(protoInj: ProtoInjector, bwv: BindingWithVisibility[]) {
|
||||
var len = bwv.length;
|
||||
|
||||
this.bindings = ListWrapper.createFixedSize(len);
|
||||
this.keyIds = ListWrapper.createFixedSize(len);
|
||||
this.visibilities = ListWrapper.createFixedSize(len);
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
this.bindings[i] = bwv[i].binding;
|
||||
this.keyIds[i] = bwv[i].getKeyId();
|
||||
this.visibilities[i] = bwv[i].visibility;
|
||||
}
|
||||
}
|
||||
|
||||
getBindingAtIndex(index: number): any {
|
||||
if (index < 0 || index >= this.bindings.length) {
|
||||
throw new OutOfBoundsError(index);
|
||||
}
|
||||
return this.bindings[index];
|
||||
}
|
||||
|
||||
createInjectorStrategy(ei: Injector): InjectorStrategy {
|
||||
return new InjectorDynamicStrategy(this, ei);
|
||||
}
|
||||
}
|
||||
|
||||
export class ProtoInjector {
|
||||
_strategy: ProtoInjectorStrategy;
|
||||
numberOfBindings: number;
|
||||
|
||||
constructor(bwv: BindingWithVisibility[]) {
|
||||
this.numberOfBindings = bwv.length;
|
||||
this._strategy = bwv.length > _MAX_CONSTRUCTION_COUNTER ?
|
||||
new ProtoInjectorDynamicStrategy(this, bwv) :
|
||||
new ProtoInjectorInlineStrategy(this, bwv);
|
||||
}
|
||||
|
||||
getBindingAtIndex(index: number): any { return this._strategy.getBindingAtIndex(index); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface InjectorStrategy {
|
||||
getObjByKeyId(keyId: number, visibility: Visibility): any;
|
||||
getObjAtIndex(index: number): any;
|
||||
getMaxNumberOfObjects(): number;
|
||||
|
||||
attach(parent: Injector, isHost: boolean): void;
|
||||
resetConstructionCounter(): void;
|
||||
instantiateBinding(binding: ResolvedBinding, visibility: Visibility): any;
|
||||
}
|
||||
|
||||
export class InjectorInlineStrategy implements InjectorStrategy {
|
||||
obj0: any = UNDEFINED;
|
||||
obj1: any = UNDEFINED;
|
||||
obj2: any = UNDEFINED;
|
||||
obj3: any = UNDEFINED;
|
||||
obj4: any = UNDEFINED;
|
||||
obj5: any = UNDEFINED;
|
||||
obj6: any = UNDEFINED;
|
||||
obj7: any = UNDEFINED;
|
||||
obj8: any = UNDEFINED;
|
||||
obj9: any = UNDEFINED;
|
||||
|
||||
constructor(public injector: Injector, public protoStrategy: ProtoInjectorInlineStrategy) {}
|
||||
|
||||
resetConstructionCounter(): void { this.injector._constructionCounter = 0; }
|
||||
|
||||
instantiateBinding(binding: ResolvedBinding, visibility: Visibility): any {
|
||||
return this.injector._new(binding, visibility);
|
||||
}
|
||||
|
||||
attach(parent: Injector, isHost: boolean): void {
|
||||
var inj = this.injector;
|
||||
inj._parent = parent;
|
||||
inj._isHost = isHost;
|
||||
}
|
||||
|
||||
getObjByKeyId(keyId: number, visibility: Visibility): any {
|
||||
var p = this.protoStrategy;
|
||||
var inj = this.injector;
|
||||
|
||||
if (p.keyId0 === keyId && canSee(p.visibility0, visibility)) {
|
||||
if (this.obj0 === UNDEFINED) {
|
||||
this.obj0 = inj._new(p.binding0, p.visibility0);
|
||||
}
|
||||
return this.obj0;
|
||||
}
|
||||
if (p.keyId1 === keyId && canSee(p.visibility1, visibility)) {
|
||||
if (this.obj1 === UNDEFINED) {
|
||||
this.obj1 = inj._new(p.binding1, p.visibility1);
|
||||
}
|
||||
return this.obj1;
|
||||
}
|
||||
if (p.keyId2 === keyId && canSee(p.visibility2, visibility)) {
|
||||
if (this.obj2 === UNDEFINED) {
|
||||
this.obj2 = inj._new(p.binding2, p.visibility2);
|
||||
}
|
||||
return this.obj2;
|
||||
}
|
||||
if (p.keyId3 === keyId && canSee(p.visibility3, visibility)) {
|
||||
if (this.obj3 === UNDEFINED) {
|
||||
this.obj3 = inj._new(p.binding3, p.visibility3);
|
||||
}
|
||||
return this.obj3;
|
||||
}
|
||||
if (p.keyId4 === keyId && canSee(p.visibility4, visibility)) {
|
||||
if (this.obj4 === UNDEFINED) {
|
||||
this.obj4 = inj._new(p.binding4, p.visibility4);
|
||||
}
|
||||
return this.obj4;
|
||||
}
|
||||
if (p.keyId5 === keyId && canSee(p.visibility5, visibility)) {
|
||||
if (this.obj5 === UNDEFINED) {
|
||||
this.obj5 = inj._new(p.binding5, p.visibility5);
|
||||
}
|
||||
return this.obj5;
|
||||
}
|
||||
if (p.keyId6 === keyId && canSee(p.visibility6, visibility)) {
|
||||
if (this.obj6 === UNDEFINED) {
|
||||
this.obj6 = inj._new(p.binding6, p.visibility6);
|
||||
}
|
||||
return this.obj6;
|
||||
}
|
||||
if (p.keyId7 === keyId && canSee(p.visibility7, visibility)) {
|
||||
if (this.obj7 === UNDEFINED) {
|
||||
this.obj7 = inj._new(p.binding7, p.visibility7);
|
||||
}
|
||||
return this.obj7;
|
||||
}
|
||||
if (p.keyId8 === keyId && canSee(p.visibility8, visibility)) {
|
||||
if (this.obj8 === UNDEFINED) {
|
||||
this.obj8 = inj._new(p.binding8, p.visibility8);
|
||||
}
|
||||
return this.obj8;
|
||||
}
|
||||
if (p.keyId9 === keyId && canSee(p.visibility9, visibility)) {
|
||||
if (this.obj9 === UNDEFINED) {
|
||||
this.obj9 = inj._new(p.binding9, p.visibility9);
|
||||
}
|
||||
return this.obj9;
|
||||
}
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
getObjAtIndex(index: number): any {
|
||||
if (index == 0) return this.obj0;
|
||||
if (index == 1) return this.obj1;
|
||||
if (index == 2) return this.obj2;
|
||||
if (index == 3) return this.obj3;
|
||||
if (index == 4) return this.obj4;
|
||||
if (index == 5) return this.obj5;
|
||||
if (index == 6) return this.obj6;
|
||||
if (index == 7) return this.obj7;
|
||||
if (index == 8) return this.obj8;
|
||||
if (index == 9) return this.obj9;
|
||||
throw new OutOfBoundsError(index);
|
||||
}
|
||||
|
||||
getMaxNumberOfObjects(): number { return _MAX_CONSTRUCTION_COUNTER; }
|
||||
}
|
||||
|
||||
|
||||
export class InjectorDynamicStrategy implements InjectorStrategy {
|
||||
objs: any[];
|
||||
|
||||
constructor(public protoStrategy: ProtoInjectorDynamicStrategy, public injector: Injector) {
|
||||
this.objs = ListWrapper.createFixedSize(protoStrategy.bindings.length);
|
||||
ListWrapper.fill(this.objs, UNDEFINED);
|
||||
}
|
||||
|
||||
resetConstructionCounter(): void { this.injector._constructionCounter = 0; }
|
||||
|
||||
instantiateBinding(binding: ResolvedBinding, visibility: Visibility): any {
|
||||
return this.injector._new(binding, visibility);
|
||||
}
|
||||
|
||||
attach(parent: Injector, isHost: boolean): void {
|
||||
var inj = this.injector;
|
||||
inj._parent = parent;
|
||||
inj._isHost = isHost;
|
||||
}
|
||||
|
||||
getObjByKeyId(keyId: number, visibility: Visibility): any {
|
||||
var p = this.protoStrategy;
|
||||
|
||||
for (var i = 0; i < p.keyIds.length; i++) {
|
||||
if (p.keyIds[i] === keyId && canSee(p.visibilities[i], visibility)) {
|
||||
if (this.objs[i] === UNDEFINED) {
|
||||
this.objs[i] = this.injector._new(p.bindings[i], p.visibilities[i]);
|
||||
}
|
||||
|
||||
return this.objs[i];
|
||||
}
|
||||
}
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
getObjAtIndex(index: number): any {
|
||||
if (index < 0 || index >= this.objs.length) {
|
||||
throw new OutOfBoundsError(index);
|
||||
}
|
||||
|
||||
return this.objs[index];
|
||||
}
|
||||
|
||||
getMaxNumberOfObjects(): number { return this.objs.length; }
|
||||
}
|
||||
|
||||
export class BindingWithVisibility {
|
||||
constructor(public binding: ResolvedBinding, public visibility: Visibility){};
|
||||
|
||||
getKeyId(): number { return this.binding.key.id; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to provide dependencies that cannot be easily expressed as bindings.
|
||||
*/
|
||||
export interface DependencyProvider {
|
||||
getDependency(injector: Injector, binding: ResolvedBinding, dependency: Dependency): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* A dependency injection container used for resolving dependencies.
|
||||
*
|
||||
* An `Injector` is a replacement for a `new` operator, which can automatically resolve the
|
||||
* constructor dependencies.
|
||||
* In typical use, application code asks for the dependencies in the constructor and they are
|
||||
* resolved by the `Injector`.
|
||||
*
|
||||
* ## Example:
|
||||
*
|
||||
* Suppose that we want to inject an `Engine` into class `Car`, we would define it like this:
|
||||
*
|
||||
* ```javascript
|
||||
* class Engine {
|
||||
* }
|
||||
*
|
||||
* class Car {
|
||||
* constructor(@Inject(Engine) engine) {
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* Next we need to write the code that creates and instantiates the `Injector`. We then ask for the
|
||||
* `root` object, `Car`, so that the `Injector` can recursively build all of that object's
|
||||
*dependencies.
|
||||
*
|
||||
* ```javascript
|
||||
* main() {
|
||||
* var injector = Injector.resolveAndCreate([Car, Engine]);
|
||||
*
|
||||
* // Get a reference to the `root` object, which will recursively instantiate the tree.
|
||||
* var car = injector.get(Car);
|
||||
* }
|
||||
* ```
|
||||
* Notice that we don't use the `new` operator because we explicitly want to have the `Injector`
|
||||
* resolve all of the object's dependencies automatically.
|
||||
*/
|
||||
export class Injector {
|
||||
/**
|
||||
* Turns a list of binding definitions into an internal resolved list of resolved bindings.
|
||||
*
|
||||
* A resolution is a process of flattening multiple nested lists and converting individual
|
||||
* bindings into a list of {@link ResolvedBinding}s. The resolution can be cached by `resolve`
|
||||
* for the {@link Injector} for performance-sensitive code.
|
||||
*
|
||||
* @param `bindings` can be a list of `Type`, {@link Binding}, {@link ResolvedBinding}, or a
|
||||
* recursive list of more bindings.
|
||||
*
|
||||
* The returned list is sparse, indexed by `id` for the {@link Key}. It is generally not useful to
|
||||
*application code
|
||||
* other than for passing it to {@link Injector} functions that require resolved binding lists,
|
||||
*such as
|
||||
* `fromResolvedBindings` and `createChildFromResolved`.
|
||||
*/
|
||||
static resolve(bindings: List<Type | Binding | List<any>>): List<ResolvedBinding> {
|
||||
var resolvedBindings = _resolveBindings(bindings);
|
||||
var flatten = _flattenBindings(resolvedBindings, new Map());
|
||||
return _createListOfBindings(flatten);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves bindings and creates an injector based on those bindings. This function is slower than
|
||||
* the corresponding `fromResolvedBindings` because it needs to resolve bindings first. See
|
||||
*`resolve`
|
||||
* for the {@link Injector}.
|
||||
*
|
||||
* Prefer `fromResolvedBindings` in performance-critical code that creates lots of injectors.
|
||||
*
|
||||
* @param `bindings` can be a list of `Type`, {@link Binding}, {@link ResolvedBinding}, or a
|
||||
*recursive list of more
|
||||
* bindings.
|
||||
* @param `depProvider`
|
||||
*/
|
||||
static resolveAndCreate(bindings: List<Type | Binding | List<any>>,
|
||||
depProvider: DependencyProvider = null): Injector {
|
||||
var resolvedBindings = Injector.resolve(bindings);
|
||||
return Injector.fromResolvedBindings(resolvedBindings, depProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an injector from previously resolved bindings. This bypasses resolution and flattening.
|
||||
* This API is the recommended way to construct injectors in performance-sensitive parts.
|
||||
*
|
||||
* @param `bindings` A sparse list of {@link ResolvedBinding}s. See `resolve` for the
|
||||
* {@link Injector}.
|
||||
* @param `depProvider`
|
||||
*/
|
||||
static fromResolvedBindings(bindings: List<ResolvedBinding>,
|
||||
depProvider: DependencyProvider = null): Injector {
|
||||
var bd = bindings.map(b => new BindingWithVisibility(b, Visibility.Public));
|
||||
var proto = new ProtoInjector(bd);
|
||||
var inj = new Injector(proto, null, depProvider);
|
||||
return inj;
|
||||
}
|
||||
|
||||
_strategy: InjectorStrategy;
|
||||
_isHost: boolean = false;
|
||||
_constructionCounter: number = 0;
|
||||
|
||||
constructor(public _proto: ProtoInjector, public _parent: Injector = null,
|
||||
private _depProvider: DependencyProvider = null,
|
||||
private _debugContext: Function = null) {
|
||||
this._strategy = _proto._strategy.createInjectorStrategy(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns debug information about the injector.
|
||||
*
|
||||
* This information is included into exceptions thrown by the injector.
|
||||
*/
|
||||
debugContext(): any { return this._debugContext(); }
|
||||
|
||||
/**
|
||||
* Retrieves an instance from the injector.
|
||||
*
|
||||
* @param `token`: usually the `Type` of an object. (Same as the token used while setting up a
|
||||
*binding).
|
||||
* @returns an instance represented by the token. Throws if not found.
|
||||
*/
|
||||
get(token: any): any {
|
||||
return this._getByKey(Key.get(token), null, null, false, Visibility.PublicAndPrivate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an instance from the injector.
|
||||
*
|
||||
* @param `token`: usually a `Type`. (Same as the token used while setting up a binding).
|
||||
* @returns an instance represented by the token. Returns `null` if not found.
|
||||
*/
|
||||
getOptional(token: any): any {
|
||||
return this._getByKey(Key.get(token), null, null, true, Visibility.PublicAndPrivate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an instance from the injector.
|
||||
*
|
||||
* @param `index`: index of an instance.
|
||||
* @returns an instance represented by the index. Throws if not found.
|
||||
*/
|
||||
getAt(index: number): any { return this._strategy.getObjAtIndex(index); }
|
||||
|
||||
/**
|
||||
* Direct parent of this injector.
|
||||
*/
|
||||
get parent(): Injector { return this._parent; }
|
||||
|
||||
/**
|
||||
* Internal. Do not use.
|
||||
*
|
||||
* We return `any` not to export the InjectorStrategy type.
|
||||
*/
|
||||
get internalStrategy(): any { return this._strategy; }
|
||||
|
||||
/**
|
||||
* Creates a child injector and loads a new set of bindings into it.
|
||||
*
|
||||
* A resolution is a process of flattening multiple nested lists and converting individual
|
||||
* bindings into a list of {@link ResolvedBinding}s. The resolution can be cached by `resolve`
|
||||
* for the {@link Injector} for performance-sensitive code.
|
||||
*
|
||||
* @param `bindings` can be a list of `Type`, {@link Binding}, {@link ResolvedBinding}, or a
|
||||
* recursive list of more bindings.
|
||||
* @param `depProvider`
|
||||
*/
|
||||
resolveAndCreateChild(bindings: List<Type | Binding | List<any>>,
|
||||
depProvider: DependencyProvider = null): Injector {
|
||||
var resovledBindings = Injector.resolve(bindings);
|
||||
return this.createChildFromResolved(resovledBindings, depProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a child injector and loads a new set of {@link ResolvedBinding}s into it.
|
||||
*
|
||||
* @param `bindings`: A sparse list of {@link ResolvedBinding}s.
|
||||
* See `resolve` for the {@link Injector}.
|
||||
* @param `depProvider`
|
||||
* @returns a new child {@link Injector}.
|
||||
*/
|
||||
createChildFromResolved(bindings: List<ResolvedBinding>,
|
||||
depProvider: DependencyProvider = null): Injector {
|
||||
var bd = bindings.map(b => new BindingWithVisibility(b, Visibility.Public));
|
||||
var proto = new ProtoInjector(bd);
|
||||
var inj = new Injector(proto, null, depProvider);
|
||||
inj._parent = this;
|
||||
return inj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a binding and instantiates an object in the context of the injector.
|
||||
*
|
||||
* @param `binding`: either a type or a binding.
|
||||
* @returns an object created using binding.
|
||||
*/
|
||||
resolveAndInstantiate(binding: Type | Binding): any {
|
||||
return this.instantiateResolved(Injector.resolve([binding])[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates an object using a resolved bindin in the context of the injector.
|
||||
*
|
||||
* @param `binding`: a resolved binding
|
||||
* @returns an object created using binding.
|
||||
*/
|
||||
instantiateResolved(binding: ResolvedBinding): any {
|
||||
return this._instantiate(binding, Visibility.PublicAndPrivate);
|
||||
}
|
||||
|
||||
_new(binding: ResolvedBinding, visibility: Visibility): any {
|
||||
if (this._constructionCounter++ > this._strategy.getMaxNumberOfObjects()) {
|
||||
throw new CyclicDependencyError(this, binding.key);
|
||||
}
|
||||
return this._instantiate(binding, visibility);
|
||||
}
|
||||
|
||||
private _instantiate(binding: ResolvedBinding, visibility: Visibility): any {
|
||||
var factory = binding.factory;
|
||||
var deps = binding.dependencies;
|
||||
var length = deps.length;
|
||||
|
||||
var d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17, d18, d19;
|
||||
try {
|
||||
d0 = length > 0 ? this._getByDependency(binding, deps[0], visibility) : null;
|
||||
d1 = length > 1 ? this._getByDependency(binding, deps[1], visibility) : null;
|
||||
d2 = length > 2 ? this._getByDependency(binding, deps[2], visibility) : null;
|
||||
d3 = length > 3 ? this._getByDependency(binding, deps[3], visibility) : null;
|
||||
d4 = length > 4 ? this._getByDependency(binding, deps[4], visibility) : null;
|
||||
d5 = length > 5 ? this._getByDependency(binding, deps[5], visibility) : null;
|
||||
d6 = length > 6 ? this._getByDependency(binding, deps[6], visibility) : null;
|
||||
d7 = length > 7 ? this._getByDependency(binding, deps[7], visibility) : null;
|
||||
d8 = length > 8 ? this._getByDependency(binding, deps[8], visibility) : null;
|
||||
d9 = length > 9 ? this._getByDependency(binding, deps[9], visibility) : null;
|
||||
d10 = length > 10 ? this._getByDependency(binding, deps[10], visibility) : null;
|
||||
d11 = length > 11 ? this._getByDependency(binding, deps[11], visibility) : null;
|
||||
d12 = length > 12 ? this._getByDependency(binding, deps[12], visibility) : null;
|
||||
d13 = length > 13 ? this._getByDependency(binding, deps[13], visibility) : null;
|
||||
d14 = length > 14 ? this._getByDependency(binding, deps[14], visibility) : null;
|
||||
d15 = length > 15 ? this._getByDependency(binding, deps[15], visibility) : null;
|
||||
d16 = length > 16 ? this._getByDependency(binding, deps[16], visibility) : null;
|
||||
d17 = length > 17 ? this._getByDependency(binding, deps[17], visibility) : null;
|
||||
d18 = length > 18 ? this._getByDependency(binding, deps[18], visibility) : null;
|
||||
d19 = length > 19 ? this._getByDependency(binding, deps[19], visibility) : null;
|
||||
} catch (e) {
|
||||
if (e instanceof AbstractBindingError) {
|
||||
e.addKey(this, binding.key);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
var obj;
|
||||
try {
|
||||
switch (length) {
|
||||
case 0:
|
||||
obj = factory();
|
||||
break;
|
||||
case 1:
|
||||
obj = factory(d0);
|
||||
break;
|
||||
case 2:
|
||||
obj = factory(d0, d1);
|
||||
break;
|
||||
case 3:
|
||||
obj = factory(d0, d1, d2);
|
||||
break;
|
||||
case 4:
|
||||
obj = factory(d0, d1, d2, d3);
|
||||
break;
|
||||
case 5:
|
||||
obj = factory(d0, d1, d2, d3, d4);
|
||||
break;
|
||||
case 6:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5);
|
||||
break;
|
||||
case 7:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6);
|
||||
break;
|
||||
case 8:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7);
|
||||
break;
|
||||
case 9:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8);
|
||||
break;
|
||||
case 10:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9);
|
||||
break;
|
||||
case 11:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10);
|
||||
break;
|
||||
case 12:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11);
|
||||
break;
|
||||
case 13:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12);
|
||||
break;
|
||||
case 14:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13);
|
||||
break;
|
||||
case 15:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14);
|
||||
break;
|
||||
case 16:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15);
|
||||
break;
|
||||
case 17:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16);
|
||||
break;
|
||||
case 18:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16,
|
||||
d17);
|
||||
break;
|
||||
case 19:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16,
|
||||
d17, d18);
|
||||
break;
|
||||
case 20:
|
||||
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16,
|
||||
d17, d18, d19);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
throw new InstantiationError(this, e, e.stack, binding.key);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
private _getByDependency(binding: ResolvedBinding, dep: Dependency,
|
||||
bindingVisibility: Visibility): any {
|
||||
var special = isPresent(this._depProvider) ?
|
||||
this._depProvider.getDependency(this, binding, dep) :
|
||||
UNDEFINED;
|
||||
if (special !== UNDEFINED) {
|
||||
return special;
|
||||
} else {
|
||||
return this._getByKey(dep.key, dep.lowerBoundVisibility, dep.upperBoundVisibility,
|
||||
dep.optional, bindingVisibility);
|
||||
}
|
||||
}
|
||||
|
||||
private _getByKey(key: Key, lowerBoundVisibility: Object, upperBoundVisibility: Object,
|
||||
optional: boolean, bindingVisibility: Visibility): any {
|
||||
if (key === INJECTOR_KEY) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (upperBoundVisibility instanceof SelfMetadata) {
|
||||
return this._getByKeySelf(key, optional, bindingVisibility);
|
||||
|
||||
} else if (upperBoundVisibility instanceof HostMetadata) {
|
||||
return this._getByKeyHost(key, optional, bindingVisibility, lowerBoundVisibility);
|
||||
|
||||
} else {
|
||||
return this._getByKeyDefault(key, optional, bindingVisibility, lowerBoundVisibility);
|
||||
}
|
||||
}
|
||||
|
||||
_throwOrNull(key: Key, optional: boolean): any {
|
||||
if (optional) {
|
||||
return null;
|
||||
} else {
|
||||
throw new NoBindingError(this, key);
|
||||
}
|
||||
}
|
||||
|
||||
_getByKeySelf(key: Key, optional: boolean, bindingVisibility: Visibility): any {
|
||||
var obj = this._strategy.getObjByKeyId(key.id, bindingVisibility);
|
||||
return (obj !== UNDEFINED) ? obj : this._throwOrNull(key, optional);
|
||||
}
|
||||
|
||||
_getByKeyHost(key: Key, optional: boolean, bindingVisibility: Visibility,
|
||||
lowerBoundVisibility: Object): any {
|
||||
var inj = this;
|
||||
|
||||
if (lowerBoundVisibility instanceof SkipSelfMetadata) {
|
||||
if (inj._isHost) {
|
||||
return this._getPrivateDependency(key, optional, inj);
|
||||
} else {
|
||||
inj = inj._parent;
|
||||
}
|
||||
}
|
||||
|
||||
while (inj != null) {
|
||||
var obj = inj._strategy.getObjByKeyId(key.id, bindingVisibility);
|
||||
if (obj !== UNDEFINED) return obj;
|
||||
|
||||
if (isPresent(inj._parent) && inj._isHost) {
|
||||
return this._getPrivateDependency(key, optional, inj);
|
||||
} else {
|
||||
inj = inj._parent;
|
||||
}
|
||||
}
|
||||
|
||||
return this._throwOrNull(key, optional);
|
||||
}
|
||||
|
||||
_getPrivateDependency(key: Key, optional: boolean, inj: Injector): any {
|
||||
var obj = inj._parent._strategy.getObjByKeyId(key.id, Visibility.Private);
|
||||
return (obj !== UNDEFINED) ? obj : this._throwOrNull(key, optional);
|
||||
}
|
||||
|
||||
_getByKeyDefault(key: Key, optional: boolean, bindingVisibility: Visibility,
|
||||
lowerBoundVisibility: Object): any {
|
||||
var inj = this;
|
||||
|
||||
if (lowerBoundVisibility instanceof SkipSelfMetadata) {
|
||||
bindingVisibility = inj._isHost ? Visibility.PublicAndPrivate : Visibility.Public;
|
||||
inj = inj._parent;
|
||||
}
|
||||
|
||||
while (inj != null) {
|
||||
var obj = inj._strategy.getObjByKeyId(key.id, bindingVisibility);
|
||||
if (obj !== UNDEFINED) return obj;
|
||||
|
||||
bindingVisibility = inj._isHost ? Visibility.PublicAndPrivate : Visibility.Public;
|
||||
inj = inj._parent;
|
||||
}
|
||||
|
||||
return this._throwOrNull(key, optional);
|
||||
}
|
||||
|
||||
get displayName(): string {
|
||||
return `Injector(bindings: [${_mapBindings(this, b => ` "${b.key.displayName}" `).join(", ")}])`;
|
||||
}
|
||||
|
||||
toString(): string { return this.displayName; }
|
||||
}
|
||||
|
||||
var INJECTOR_KEY = Key.get(Injector);
|
||||
|
||||
|
||||
function _resolveBindings(bindings: List<Type | Binding | List<any>>): List<ResolvedBinding> {
|
||||
var resolvedList = ListWrapper.createFixedSize(bindings.length);
|
||||
for (var i = 0; i < bindings.length; i++) {
|
||||
var unresolved = resolveForwardRef(bindings[i]);
|
||||
var resolved;
|
||||
if (unresolved instanceof ResolvedBinding) {
|
||||
resolved = unresolved; // ha-ha! I'm easily amused
|
||||
} else if (unresolved instanceof Type) {
|
||||
resolved = bind(unresolved).toClass(unresolved).resolve();
|
||||
} else if (unresolved instanceof Binding) {
|
||||
resolved = unresolved.resolve();
|
||||
} else if (unresolved instanceof List) {
|
||||
resolved = _resolveBindings(unresolved);
|
||||
} else if (unresolved instanceof BindingBuilder) {
|
||||
throw new InvalidBindingError(unresolved.token);
|
||||
} else {
|
||||
throw new InvalidBindingError(unresolved);
|
||||
}
|
||||
resolvedList[i] = resolved;
|
||||
}
|
||||
return resolvedList;
|
||||
}
|
||||
|
||||
function _createListOfBindings(flattenedBindings: Map<number, ResolvedBinding>):
|
||||
List<ResolvedBinding> {
|
||||
return MapWrapper.values(flattenedBindings);
|
||||
}
|
||||
|
||||
function _flattenBindings(bindings: List<ResolvedBinding | List<any>>,
|
||||
res: Map<number, ResolvedBinding>): Map<number, ResolvedBinding> {
|
||||
ListWrapper.forEach(bindings, function(b) {
|
||||
if (b instanceof ResolvedBinding) {
|
||||
res.set(b.key.id, b);
|
||||
} else if (b instanceof List) {
|
||||
_flattenBindings(b, res);
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
function _mapBindings(injector: Injector, fn: Function): any[] {
|
||||
var res = [];
|
||||
for (var i = 0; i < injector._proto.numberOfBindings; ++i) {
|
||||
res.push(fn(injector._proto.getBindingAtIndex(i)));
|
||||
}
|
||||
return res;
|
||||
}
|
66
modules/angular2/src/core/di/key.ts
Normal file
66
modules/angular2/src/core/di/key.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import {MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {stringify, CONST, Type, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import {TypeLiteral} from './type_literal';
|
||||
import {resolveForwardRef} from './forward_ref';
|
||||
|
||||
export {TypeLiteral} from './type_literal';
|
||||
|
||||
/**
|
||||
* A unique object used for retrieving items from the {@link Injector}.
|
||||
*
|
||||
* Keys have:
|
||||
* - a system-wide unique `id`.
|
||||
* - a `token`, usually the `Type` of the instance.
|
||||
*
|
||||
* Keys are used internally by the {@link Injector} because their system-wide unique `id`s allow the
|
||||
* injector to index in arrays rather than looking up items in maps.
|
||||
*/
|
||||
export class Key {
|
||||
constructor(public token: Object, public id: number) {
|
||||
if (isBlank(token)) {
|
||||
throw new BaseException('Token must be defined!');
|
||||
}
|
||||
}
|
||||
|
||||
get displayName(): string { return stringify(this.token); }
|
||||
|
||||
/**
|
||||
* Retrieves a `Key` for a token.
|
||||
*/
|
||||
static get(token: Object): Key { return _globalKeyRegistry.get(resolveForwardRef(token)); }
|
||||
|
||||
/**
|
||||
* @returns the number of keys registered in the system.
|
||||
*/
|
||||
static get numberOfKeys(): number { return _globalKeyRegistry.numberOfKeys; }
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export class KeyRegistry {
|
||||
private _allKeys: Map<Object, Key> = new Map();
|
||||
|
||||
get(token: Object): Key {
|
||||
if (token instanceof Key) return token;
|
||||
|
||||
// TODO: workaround for https://github.com/Microsoft/TypeScript/issues/3123
|
||||
var theToken = token;
|
||||
if (token instanceof TypeLiteral) {
|
||||
theToken = token.type;
|
||||
}
|
||||
token = theToken;
|
||||
|
||||
if (this._allKeys.has(token)) {
|
||||
return this._allKeys.get(token);
|
||||
}
|
||||
|
||||
var newKey = new Key(token, Key.numberOfKeys);
|
||||
this._allKeys.set(token, newKey);
|
||||
return newKey;
|
||||
}
|
||||
|
||||
get numberOfKeys(): number { return MapWrapper.size(this._allKeys); }
|
||||
}
|
||||
|
||||
var _globalKeyRegistry = new KeyRegistry();
|
166
modules/angular2/src/core/di/metadata.ts
Normal file
166
modules/angular2/src/core/di/metadata.ts
Normal file
@ -0,0 +1,166 @@
|
||||
import {CONST, CONST_EXPR, stringify, isBlank, isPresent} from "angular2/src/facade/lang";
|
||||
|
||||
/**
|
||||
* A parameter metadata that specifies a dependency.
|
||||
*
|
||||
* ```
|
||||
* class AComponent {
|
||||
* constructor(@Inject(MyService) aService:MyService) {}
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
@CONST()
|
||||
export class InjectMetadata {
|
||||
constructor(public token) {}
|
||||
toString(): string { return `@Inject(${stringify(this.token)})`; }
|
||||
}
|
||||
|
||||
/**
|
||||
* A parameter metadata that marks a dependency as optional. {@link Injector} provides `null` if
|
||||
* the dependency is not found.
|
||||
*
|
||||
* ```
|
||||
* class AComponent {
|
||||
* constructor(@Optional() aService:MyService) {
|
||||
* this.aService = aService;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@CONST()
|
||||
export class OptionalMetadata {
|
||||
toString(): string { return `@Optional()`; }
|
||||
}
|
||||
|
||||
/**
|
||||
* `DependencyMetadata is used by the framework to extend DI.
|
||||
*
|
||||
* Only metadata implementing `DependencyMetadata` are added to the list of dependency
|
||||
* properties.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```
|
||||
* class Exclude extends DependencyMetadata {}
|
||||
* class NotDependencyProperty {}
|
||||
*
|
||||
* class AComponent {
|
||||
* constructor(@Exclude @NotDependencyProperty aService:AService) {}
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* will create the following dependency:
|
||||
*
|
||||
* ```
|
||||
* new Dependency(Key.get(AService), [new Exclude()])
|
||||
* ```
|
||||
*
|
||||
* The framework can use `new Exclude()` to handle the `aService` dependency
|
||||
* in a specific way.
|
||||
*/
|
||||
@CONST()
|
||||
export class DependencyMetadata {
|
||||
get token() { return null; }
|
||||
}
|
||||
|
||||
/**
|
||||
* A marker metadata that marks a class as available to `Injector` for creation. Used by tooling
|
||||
* for generating constructor stubs.
|
||||
*
|
||||
* ```
|
||||
* class NeedsService {
|
||||
* constructor(svc:UsefulService) {}
|
||||
* }
|
||||
*
|
||||
* @Injectable
|
||||
* class UsefulService {}
|
||||
* ```
|
||||
*/
|
||||
@CONST()
|
||||
export class InjectableMetadata {
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that an injector should retrieve a dependency from itself.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```
|
||||
* class Dependency {
|
||||
* }
|
||||
*
|
||||
* class NeedsDependency {
|
||||
* constructor(public @Self() dependency:Dependency) {}
|
||||
* }
|
||||
*
|
||||
* var inj = Injector.resolveAndCreate([Dependency, NeedsDependency]);
|
||||
* var nd = inj.get(NeedsDependency);
|
||||
* expect(nd.dependency).toBeAnInstanceOf(Dependency);
|
||||
* ```
|
||||
*/
|
||||
@CONST()
|
||||
export class SelfMetadata {
|
||||
toString(): string { return `@Self()`; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that the dependency resolution should start from the parent injector.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
*
|
||||
* ```
|
||||
* class Service {}
|
||||
*
|
||||
* class ParentService implements Service {
|
||||
* }
|
||||
*
|
||||
* class ChildService implements Service {
|
||||
* constructor(public @SkipSelf() parentService:Service) {}
|
||||
* }
|
||||
*
|
||||
* var parent = Injector.resolveAndCreate([
|
||||
* bind(Service).toClass(ParentService)
|
||||
* ]);
|
||||
* var child = parent.resolveAndCreateChild([
|
||||
* bind(Service).toClass(ChildSerice)
|
||||
* ]);
|
||||
* var s = child.get(Service);
|
||||
* expect(s).toBeAnInstanceOf(ChildService);
|
||||
* expect(s.parentService).toBeAnInstanceOf(ParentService);
|
||||
* ```
|
||||
*/
|
||||
@CONST()
|
||||
export class SkipSelfMetadata {
|
||||
toString(): string { return `@SkipSelf()`; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that an injector should retrieve a dependency from any injector until reaching the
|
||||
* closest host.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```
|
||||
* class Dependency {
|
||||
* }
|
||||
*
|
||||
* class NeedsDependency {
|
||||
* constructor(public @Host() dependency:Dependency) {}
|
||||
* }
|
||||
*
|
||||
* var parent = Injector.resolveAndCreate([
|
||||
* bind(Dependency).toClass(HostDependency)
|
||||
* ]);
|
||||
* var child = parent.resolveAndCreateChild([]);
|
||||
* var grandChild = child.resolveAndCreateChild([NeedsDependency, Depedency]);
|
||||
* var nd = grandChild.get(NeedsDependency);
|
||||
* expect(nd.dependency).toBeAnInstanceOf(HostDependency);
|
||||
* ```
|
||||
*/
|
||||
@CONST()
|
||||
export class HostMetadata {
|
||||
toString(): string { return `@Host()`; }
|
||||
}
|
10
modules/angular2/src/core/di/opaque_token.ts
Normal file
10
modules/angular2/src/core/di/opaque_token.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import {CONST} from 'angular2/src/facade/lang';
|
||||
|
||||
@CONST()
|
||||
export class OpaqueToken {
|
||||
_desc: string;
|
||||
|
||||
constructor(desc: string) { this._desc = 'Token(' + desc + ')'; }
|
||||
|
||||
toString(): string { return this._desc; }
|
||||
}
|
4
modules/angular2/src/core/di/type_info.dart
Normal file
4
modules/angular2/src/core/di/type_info.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library angular2.di.type_info;
|
||||
|
||||
// In dart always return empty, as we can get the co
|
||||
argsLength(Type type) => 0;
|
0
modules/angular2/src/core/di/type_info.ts
Normal file
0
modules/angular2/src/core/di/type_info.ts
Normal file
26
modules/angular2/src/core/di/type_literal.dart
Normal file
26
modules/angular2/src/core/di/type_literal.dart
Normal file
@ -0,0 +1,26 @@
|
||||
library angular2.di.type_literal;
|
||||
|
||||
/**
|
||||
* Use type literals as DI keys corresponding to generic types.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* Injector.resolveAndCreate([
|
||||
* bind(new TypeLiteral<List<int>>()).toValue([1, 2, 3])
|
||||
* ]);
|
||||
*
|
||||
* class Foo {
|
||||
* // Delend on `List<int>` normally.
|
||||
* Foo(List<int> list) { ... }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This capability might be added to the language one day. See:
|
||||
*
|
||||
* https://code.google.com/p/dart/issues/detail?id=11923
|
||||
*/
|
||||
class TypeLiteral<T> {
|
||||
const TypeLiteral();
|
||||
Type get type => T;
|
||||
}
|
7
modules/angular2/src/core/di/type_literal.ts
Normal file
7
modules/angular2/src/core/di/type_literal.ts
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Type literals is a Dart-only feature. This is here only so we can x-compile
|
||||
* to multiple languages.
|
||||
*/
|
||||
export class TypeLiteral {
|
||||
get type(): any { throw new Error("Type literals are only supported in Dart"); }
|
||||
}
|
130
modules/angular2/src/core/directives/ng_class.ts
Normal file
130
modules/angular2/src/core/directives/ng_class.ts
Normal file
@ -0,0 +1,130 @@
|
||||
import {isPresent, isString, StringWrapper, isBlank} from 'angular2/src/facade/lang';
|
||||
import {Directive, LifecycleEvent} from 'angular2/metadata';
|
||||
import {ElementRef} from 'angular2/core';
|
||||
import {Renderer} from 'angular2/src/render/api';
|
||||
import {
|
||||
KeyValueDiffer,
|
||||
IterableDiffer,
|
||||
IterableDiffers,
|
||||
KeyValueDiffers
|
||||
} from 'angular2/change_detection';
|
||||
import {ListWrapper, StringMapWrapper, isListLikeIterable} from 'angular2/src/facade/collection';
|
||||
|
||||
/**
|
||||
* Adds and removes CSS classes based on an {expression} value.
|
||||
*
|
||||
* The result of expression is used to add and remove CSS classes using the following logic,
|
||||
* based on expression's value type:
|
||||
* - {string} - all the CSS classes (space - separated) are added
|
||||
* - {Array} - all the CSS classes (Array elements) are added
|
||||
* - {Object} - each key corresponds to a CSS class name while values
|
||||
* are interpreted as {boolean} expression. If a given expression
|
||||
* evaluates to {true} a corresponding CSS class is added - otherwise
|
||||
* it is removed.
|
||||
*
|
||||
* # Example:
|
||||
*
|
||||
* ```
|
||||
* <div class="message" [ng-class]="{error: errorCount > 0}">
|
||||
* Please check errors.
|
||||
* </div>
|
||||
* ```
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ng-class]',
|
||||
lifecycle: [LifecycleEvent.onCheck, LifecycleEvent.onDestroy],
|
||||
properties: ['rawClass: ng-class', 'initialClasses: class']
|
||||
})
|
||||
export class NgClass {
|
||||
private _differ: any;
|
||||
private _mode: string;
|
||||
private _initialClasses = [];
|
||||
private _rawClass;
|
||||
|
||||
constructor(private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
|
||||
private _ngEl: ElementRef, private _renderer: Renderer) {}
|
||||
|
||||
set initialClasses(v) {
|
||||
this._applyInitialClasses(true);
|
||||
this._initialClasses = isPresent(v) && isString(v) ? v.split(' ') : [];
|
||||
this._applyInitialClasses(false);
|
||||
this._applyClasses(this._rawClass, false);
|
||||
}
|
||||
|
||||
set rawClass(v) {
|
||||
this._cleanupClasses(this._rawClass);
|
||||
|
||||
if (isString(v)) {
|
||||
v = v.split(' ');
|
||||
}
|
||||
|
||||
this._rawClass = v;
|
||||
if (isPresent(v)) {
|
||||
if (isListLikeIterable(v)) {
|
||||
this._differ = this._iterableDiffers.find(v).create(null);
|
||||
this._mode = 'iterable';
|
||||
} else {
|
||||
this._differ = this._keyValueDiffers.find(v).create(null);
|
||||
this._mode = 'keyValue';
|
||||
}
|
||||
} else {
|
||||
this._differ = null;
|
||||
}
|
||||
}
|
||||
|
||||
onCheck(): void {
|
||||
if (isPresent(this._differ)) {
|
||||
var changes = this._differ.diff(this._rawClass);
|
||||
if (isPresent(changes)) {
|
||||
if (this._mode == 'iterable') {
|
||||
this._applyIterableChanges(changes);
|
||||
} else {
|
||||
this._applyKeyValueChanges(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(): void { this._cleanupClasses(this._rawClass); }
|
||||
|
||||
private _cleanupClasses(rawClassVal): void {
|
||||
this._applyClasses(rawClassVal, true);
|
||||
this._applyInitialClasses(false);
|
||||
}
|
||||
|
||||
private _applyKeyValueChanges(changes: any): void {
|
||||
changes.forEachAddedItem((record) => { this._toggleClass(record.key, record.currentValue); });
|
||||
changes.forEachChangedItem((record) => { this._toggleClass(record.key, record.currentValue); });
|
||||
changes.forEachRemovedItem((record) => {
|
||||
if (record.previousValue) {
|
||||
this._toggleClass(record.key, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _applyIterableChanges(changes: any): void {
|
||||
changes.forEachAddedItem((record) => { this._toggleClass(record.item, true); });
|
||||
changes.forEachRemovedItem((record) => { this._toggleClass(record.item, false); });
|
||||
}
|
||||
|
||||
private _applyInitialClasses(isCleanup: boolean) {
|
||||
ListWrapper.forEach(this._initialClasses,
|
||||
(className) => { this._toggleClass(className, !isCleanup); });
|
||||
}
|
||||
|
||||
private _applyClasses(rawClassVal, isCleanup: boolean) {
|
||||
if (isPresent(rawClassVal)) {
|
||||
if (isListLikeIterable(rawClassVal)) {
|
||||
ListWrapper.forEach(rawClassVal, (className) => this._toggleClass(className, !isCleanup));
|
||||
} else {
|
||||
StringMapWrapper.forEach(rawClassVal, (expVal, className) => {
|
||||
if (expVal) this._toggleClass(className, !isCleanup);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleClass(className: string, enabled): void {
|
||||
this._renderer.setElementClass(this._ngEl, className, enabled);
|
||||
}
|
||||
}
|
125
modules/angular2/src/core/directives/ng_for.ts
Normal file
125
modules/angular2/src/core/directives/ng_for.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import {Directive, LifecycleEvent} from 'angular2/metadata';
|
||||
import {ViewContainerRef, ViewRef, TemplateRef} from 'angular2/core';
|
||||
import {ChangeDetectorRef, IterableDiffer, IterableDiffers} from 'angular2/change_detection';
|
||||
import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
* The `NgFor` directive instantiates a template once per item from an iterable. The context for
|
||||
* each instantiated template inherits from the outer context with the given loop variable set
|
||||
* to the current item from the iterable.
|
||||
*
|
||||
* It is possible to alias the `index` to a local variable that will be set to the current loop
|
||||
* iteration in the template context.
|
||||
*
|
||||
* When the contents of the iterator changes, `NgFor` makes the corresponding changes to the DOM:
|
||||
*
|
||||
* * When an item is added, a new instance of the template is added to the DOM.
|
||||
* * When an item is removed, its template instance is removed from the DOM.
|
||||
* * When items are reordered, their respective templates are reordered in the DOM.
|
||||
*
|
||||
* # Example
|
||||
*
|
||||
* ```
|
||||
* <ul>
|
||||
* <li *ng-for="#error of errors; #i = index">
|
||||
* Error {{i}} of {{errors.length}}: {{error.message}}
|
||||
* </li>
|
||||
* </ul>
|
||||
* ```
|
||||
*
|
||||
* # Syntax
|
||||
*
|
||||
* - `<li *ng-for="#item of items; #i = index">...</li>`
|
||||
* - `<li template="ng-for #item of items; #i = index">...</li>`
|
||||
* - `<template ng-for #item [ng-for-of]="items" #i="index"><li>...</li></template>`
|
||||
*/
|
||||
@Directive(
|
||||
{selector: '[ng-for][ng-for-of]', properties: ['ngForOf'], lifecycle: [LifecycleEvent.onCheck]})
|
||||
export class NgFor {
|
||||
_ngForOf: any;
|
||||
private _differ: IterableDiffer;
|
||||
|
||||
constructor(private viewContainer: ViewContainerRef, private templateRef: TemplateRef,
|
||||
private iterableDiffers: IterableDiffers, private cdr: ChangeDetectorRef) {}
|
||||
|
||||
set ngForOf(value: any) {
|
||||
this._ngForOf = value;
|
||||
if (isBlank(this._differ) && isPresent(value)) {
|
||||
this._differ = this.iterableDiffers.find(value).create(this.cdr);
|
||||
}
|
||||
}
|
||||
|
||||
onCheck() {
|
||||
if (isPresent(this._differ)) {
|
||||
var changes = this._differ.diff(this._ngForOf);
|
||||
if (isPresent(changes)) this._applyChanges(changes);
|
||||
}
|
||||
}
|
||||
|
||||
private _applyChanges(changes) {
|
||||
// TODO(rado): check if change detection can produce a change record that is
|
||||
// easier to consume than current.
|
||||
var recordViewTuples = [];
|
||||
changes.forEachRemovedItem((removedRecord) =>
|
||||
recordViewTuples.push(new RecordViewTuple(removedRecord, null)));
|
||||
|
||||
changes.forEachMovedItem((movedRecord) =>
|
||||
recordViewTuples.push(new RecordViewTuple(movedRecord, null)));
|
||||
|
||||
var insertTuples = NgFor.bulkRemove(recordViewTuples, this.viewContainer);
|
||||
|
||||
changes.forEachAddedItem((addedRecord) =>
|
||||
insertTuples.push(new RecordViewTuple(addedRecord, null)));
|
||||
|
||||
NgFor.bulkInsert(insertTuples, this.viewContainer, this.templateRef);
|
||||
|
||||
for (var i = 0; i < insertTuples.length; i++) {
|
||||
this._perViewChange(insertTuples[i].view, insertTuples[i].record);
|
||||
}
|
||||
}
|
||||
|
||||
private _perViewChange(view, record) {
|
||||
view.setLocal('\$implicit', record.item);
|
||||
view.setLocal('index', record.currentIndex);
|
||||
}
|
||||
|
||||
static bulkRemove(tuples: List<RecordViewTuple>,
|
||||
viewContainer: ViewContainerRef): List<RecordViewTuple> {
|
||||
tuples.sort((a, b) => a.record.previousIndex - b.record.previousIndex);
|
||||
var movedTuples = [];
|
||||
for (var i = tuples.length - 1; i >= 0; i--) {
|
||||
var tuple = tuples[i];
|
||||
// separate moved views from removed views.
|
||||
if (isPresent(tuple.record.currentIndex)) {
|
||||
tuple.view = viewContainer.detach(tuple.record.previousIndex);
|
||||
movedTuples.push(tuple);
|
||||
} else {
|
||||
viewContainer.remove(tuple.record.previousIndex);
|
||||
}
|
||||
}
|
||||
return movedTuples;
|
||||
}
|
||||
|
||||
static bulkInsert(tuples: List<RecordViewTuple>, viewContainer: ViewContainerRef,
|
||||
templateRef: TemplateRef): List<RecordViewTuple> {
|
||||
tuples.sort((a, b) => a.record.currentIndex - b.record.currentIndex);
|
||||
for (var i = 0; i < tuples.length; i++) {
|
||||
var tuple = tuples[i];
|
||||
if (isPresent(tuple.view)) {
|
||||
viewContainer.insert(tuple.view, tuple.record.currentIndex);
|
||||
} else {
|
||||
tuple.view = viewContainer.createEmbeddedView(templateRef, tuple.record.currentIndex);
|
||||
}
|
||||
}
|
||||
return tuples;
|
||||
}
|
||||
}
|
||||
|
||||
export class RecordViewTuple {
|
||||
view: ViewRef;
|
||||
record: any;
|
||||
constructor(record, view) {
|
||||
this.record = record;
|
||||
this.view = view;
|
||||
}
|
||||
}
|
42
modules/angular2/src/core/directives/ng_if.ts
Normal file
42
modules/angular2/src/core/directives/ng_if.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import {Directive} from 'angular2/metadata';
|
||||
import {ViewContainerRef, TemplateRef} from 'angular2/core';
|
||||
import {isBlank} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
* Removes or recreates a portion of the DOM tree based on an {expression}.
|
||||
*
|
||||
* If the expression assigned to `ng-if` evaluates to a false value then the element
|
||||
* is removed from the DOM, otherwise a clone of the element is reinserted into the DOM.
|
||||
*
|
||||
* # Example:
|
||||
*
|
||||
* ```
|
||||
* <div *ng-if="errorCount > 0" class="error">
|
||||
* <!-- Error message displayed when the errorCount property on the current context is greater
|
||||
* than 0. -->
|
||||
* {{errorCount}} errors detected
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* # Syntax
|
||||
*
|
||||
* - `<div *ng-if="condition">...</div>`
|
||||
* - `<div template="ng-if condition">...</div>`
|
||||
* - `<template [ng-if]="condition"><div>...</div></template>`
|
||||
*/
|
||||
@Directive({selector: '[ng-if]', properties: ['ngIf']})
|
||||
export class NgIf {
|
||||
private _prevCondition: boolean = null;
|
||||
|
||||
constructor(private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef) {}
|
||||
|
||||
set ngIf(newCondition /* boolean */) {
|
||||
if (newCondition && (isBlank(this._prevCondition) || !this._prevCondition)) {
|
||||
this._prevCondition = true;
|
||||
this._viewContainer.createEmbeddedView(this._templateRef);
|
||||
} else if (!newCondition && (isBlank(this._prevCondition) || this._prevCondition)) {
|
||||
this._prevCondition = false;
|
||||
this._viewContainer.clear();
|
||||
}
|
||||
}
|
||||
}
|
18
modules/angular2/src/core/directives/ng_non_bindable.ts
Normal file
18
modules/angular2/src/core/directives/ng_non_bindable.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {Directive} from 'angular2/metadata';
|
||||
|
||||
/**
|
||||
* The `NgNonBindable` directive tells Angular not to compile or bind the contents of the current
|
||||
* DOM element. This is useful if the element contains what appears to be Angular directives and
|
||||
* bindings but which should be ignored by Angular. This could be the case if you have a site that
|
||||
* displays snippets of code, for instance.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* <div>Normal: {{1 + 2}}</div> // output "Normal: 3"
|
||||
* <div ng-non-bindable>Ignored: {{1 + 2}}</div> // output "Ignored: {{1 + 2}}"
|
||||
* ```
|
||||
*/
|
||||
@Directive({selector: '[ng-non-bindable]', compileChildren: false})
|
||||
export class NgNonBindable {
|
||||
}
|
65
modules/angular2/src/core/directives/ng_style.ts
Normal file
65
modules/angular2/src/core/directives/ng_style.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import {Directive, LifecycleEvent} from 'angular2/metadata';
|
||||
import {ElementRef} from 'angular2/core';
|
||||
import {KeyValueDiffer, KeyValueDiffers} from 'angular2/change_detection';
|
||||
import {isPresent, isBlank, print} from 'angular2/src/facade/lang';
|
||||
import {Renderer} from 'angular2/src/render/api';
|
||||
|
||||
/**
|
||||
* Adds or removes styles based on an {expression}.
|
||||
*
|
||||
* When the expression assigned to `ng-style` evaluates to an object, the corresponding element
|
||||
* styles are updated. Style names to update are taken from the object keys and values - from the
|
||||
* corresponding object values.
|
||||
*
|
||||
* # Example:
|
||||
*
|
||||
* ```
|
||||
* <div [ng-style]="{'text-align': alignExp}"></div>
|
||||
* ```
|
||||
*
|
||||
* In the above example the `text-align` style will be updated based on the `alignExp` value
|
||||
* changes.
|
||||
*
|
||||
* # Syntax
|
||||
*
|
||||
* - `<div [ng-style]="{'text-align': alignExp}"></div>`
|
||||
* - `<div [ng-style]="styleExp"></div>`
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ng-style]',
|
||||
lifecycle: [LifecycleEvent.onCheck],
|
||||
properties: ['rawStyle: ng-style']
|
||||
})
|
||||
export class NgStyle {
|
||||
_rawStyle;
|
||||
_differ: KeyValueDiffer;
|
||||
|
||||
constructor(private _differs: KeyValueDiffers, private _ngEl: ElementRef,
|
||||
private _renderer: Renderer) {}
|
||||
|
||||
set rawStyle(v) {
|
||||
this._rawStyle = v;
|
||||
if (isBlank(this._differ) && isPresent(v)) {
|
||||
this._differ = this._differs.find(this._rawStyle).create(null);
|
||||
}
|
||||
}
|
||||
|
||||
onCheck() {
|
||||
if (isPresent(this._differ)) {
|
||||
var changes = this._differ.diff(this._rawStyle);
|
||||
if (isPresent(changes)) {
|
||||
this._applyChanges(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _applyChanges(changes: any): void {
|
||||
changes.forEachAddedItem((record) => { this._setStyle(record.key, record.currentValue); });
|
||||
changes.forEachChangedItem((record) => { this._setStyle(record.key, record.currentValue); });
|
||||
changes.forEachRemovedItem((record) => { this._setStyle(record.key, null); });
|
||||
}
|
||||
|
||||
private _setStyle(name: string, val: string): void {
|
||||
this._renderer.setElementStyle(this._ngEl, name, val);
|
||||
}
|
||||
}
|
176
modules/angular2/src/core/directives/ng_switch.ts
Normal file
176
modules/angular2/src/core/directives/ng_switch.ts
Normal file
@ -0,0 +1,176 @@
|
||||
import {Directive} from 'angular2/metadata';
|
||||
import {Host} from 'angular2/di';
|
||||
import {ViewContainerRef, TemplateRef} from 'angular2/core';
|
||||
import {isPresent, isBlank, normalizeBlank, CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, List, Map} from 'angular2/src/facade/collection';
|
||||
|
||||
const _WHEN_DEFAULT = CONST_EXPR(new Object());
|
||||
|
||||
export class SwitchView {
|
||||
constructor(private _viewContainerRef: ViewContainerRef, private _templateRef: TemplateRef) {}
|
||||
|
||||
create(): void { this._viewContainerRef.createEmbeddedView(this._templateRef); }
|
||||
|
||||
destroy(): void { this._viewContainerRef.clear(); }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `NgSwitch` directive is used to conditionally swap DOM structure on your template based on a
|
||||
* scope expression.
|
||||
* Elements within `NgSwitch` but without `NgSwitchWhen` or `NgSwitchDefault` directives will be
|
||||
* preserved at the location as specified in the template.
|
||||
*
|
||||
* `NgSwitch` simply chooses nested elements and makes them visible based on which element matches
|
||||
* the value obtained from the evaluated expression. In other words, you define a container element
|
||||
* (where you place the directive), place an expression on the **`[ng-switch]="..."` attribute**),
|
||||
* define any inner elements inside of the directive and place a `[ng-switch-when]` attribute per
|
||||
* element.
|
||||
* The when attribute is used to inform NgSwitch which element to display when the expression is
|
||||
* evaluated. If a matching expression is not found via a when attribute then an element with the
|
||||
* default attribute is displayed.
|
||||
*
|
||||
* # Example:
|
||||
*
|
||||
* ```
|
||||
* <ANY [ng-switch]="expression">
|
||||
* <template [ng-switch-when]="whenExpression1">...</template>
|
||||
* <template [ng-switch-when]="whenExpression1">...</template>
|
||||
* <template ng-switch-default>...</template>
|
||||
* </ANY>
|
||||
* ```
|
||||
*/
|
||||
@Directive({selector: '[ng-switch]', properties: ['ngSwitch']})
|
||||
export class NgSwitch {
|
||||
private _switchValue: any;
|
||||
private _useDefault: boolean = false;
|
||||
private _valueViews: Map<any, List<SwitchView>> = new Map();
|
||||
private _activeViews: List<SwitchView> = [];
|
||||
|
||||
set ngSwitch(value) {
|
||||
// Empty the currently active ViewContainers
|
||||
this._emptyAllActiveViews();
|
||||
|
||||
// Add the ViewContainers matching the value (with a fallback to default)
|
||||
this._useDefault = false;
|
||||
var views = this._valueViews.get(value);
|
||||
if (isBlank(views)) {
|
||||
this._useDefault = true;
|
||||
views = normalizeBlank(this._valueViews.get(_WHEN_DEFAULT));
|
||||
}
|
||||
this._activateViews(views);
|
||||
|
||||
this._switchValue = value;
|
||||
}
|
||||
|
||||
_onWhenValueChanged(oldWhen, newWhen, view: SwitchView): void {
|
||||
this._deregisterView(oldWhen, view);
|
||||
this._registerView(newWhen, view);
|
||||
|
||||
if (oldWhen === this._switchValue) {
|
||||
view.destroy();
|
||||
ListWrapper.remove(this._activeViews, view);
|
||||
} else if (newWhen === this._switchValue) {
|
||||
if (this._useDefault) {
|
||||
this._useDefault = false;
|
||||
this._emptyAllActiveViews();
|
||||
}
|
||||
view.create();
|
||||
this._activeViews.push(view);
|
||||
}
|
||||
|
||||
// Switch to default when there is no more active ViewContainers
|
||||
if (this._activeViews.length === 0 && !this._useDefault) {
|
||||
this._useDefault = true;
|
||||
this._activateViews(this._valueViews.get(_WHEN_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
||||
_emptyAllActiveViews(): void {
|
||||
var activeContainers = this._activeViews;
|
||||
for (var i = 0; i < activeContainers.length; i++) {
|
||||
activeContainers[i].destroy();
|
||||
}
|
||||
this._activeViews = [];
|
||||
}
|
||||
|
||||
_activateViews(views: List<SwitchView>): void {
|
||||
// TODO(vicb): assert(this._activeViews.length === 0);
|
||||
if (isPresent(views)) {
|
||||
for (var i = 0; i < views.length; i++) {
|
||||
views[i].create();
|
||||
}
|
||||
this._activeViews = views;
|
||||
}
|
||||
}
|
||||
|
||||
_registerView(value, view: SwitchView): void {
|
||||
var views = this._valueViews.get(value);
|
||||
if (isBlank(views)) {
|
||||
views = [];
|
||||
this._valueViews.set(value, views);
|
||||
}
|
||||
views.push(view);
|
||||
}
|
||||
|
||||
_deregisterView(value, view: SwitchView): void {
|
||||
// `_WHEN_DEFAULT` is used a marker for non-registered whens
|
||||
if (value === _WHEN_DEFAULT) return;
|
||||
var views = this._valueViews.get(value);
|
||||
if (views.length == 1) {
|
||||
this._valueViews.delete(value);
|
||||
} else {
|
||||
ListWrapper.remove(views, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a case statement as an expression.
|
||||
*
|
||||
* If multiple `NgSwitchWhen` match the `NgSwitch` value, all of them are displayed.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* // match against a context variable
|
||||
* <template [ng-switch-when]="contextVariable">...</template>
|
||||
*
|
||||
* // match against a constant string
|
||||
* <template ng-switch-when="stringValue">...</template>
|
||||
* ```
|
||||
*/
|
||||
@Directive({selector: '[ng-switch-when]', properties: ['ngSwitchWhen']})
|
||||
export class NgSwitchWhen {
|
||||
// `_WHEN_DEFAULT` is used as a marker for a not yet initialized value
|
||||
_value: any = _WHEN_DEFAULT;
|
||||
_view: SwitchView;
|
||||
|
||||
constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef,
|
||||
@Host() private _switch: NgSwitch) {
|
||||
this._view = new SwitchView(viewContainer, templateRef);
|
||||
}
|
||||
|
||||
set ngSwitchWhen(value) {
|
||||
this._switch._onWhenValueChanged(this._value, value, this._view);
|
||||
this._value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a default case statement.
|
||||
*
|
||||
* Default case statements are displayed when no `NgSwitchWhen` match the `ng-switch` value.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* <template ng-switch-default>...</template>
|
||||
* ```
|
||||
*/
|
||||
@Directive({selector: '[ng-switch-default]'})
|
||||
export class NgSwitchDefault {
|
||||
constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef,
|
||||
@Host() sswitch: NgSwitch) {
|
||||
sswitch._registerView(_WHEN_DEFAULT, new SwitchView(viewContainer, templateRef));
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
library angular2.directives.observable_list_iterable_diff;
|
||||
|
||||
import 'package:observe/observe.dart' show ObservableList;
|
||||
import 'package:angular2/change_detection.dart';
|
||||
import 'package:angular2/src/change_detection/differs/default_iterable_differ.dart';
|
||||
import 'dart:async';
|
||||
|
||||
class ObservableListDiff extends DefaultIterableDiffer {
|
||||
ChangeDetectorRef _ref;
|
||||
ObservableListDiff(this._ref);
|
||||
|
||||
bool _updated = true;
|
||||
ObservableList _collection;
|
||||
StreamSubscription _subscription;
|
||||
|
||||
onDestroy() {
|
||||
if (this._subscription != null) {
|
||||
this._subscription.cancel();
|
||||
this._subscription = null;
|
||||
this._collection = null;
|
||||
}
|
||||
}
|
||||
|
||||
dynamic diff(ObservableList collection) {
|
||||
if (collection is! ObservableList) {
|
||||
throw "Cannot change the type of a collection";
|
||||
}
|
||||
|
||||
// A new collection instance is passed in.
|
||||
// - We need to set up a listener.
|
||||
// - We need to diff collection.
|
||||
if (!identical(_collection, collection)) {
|
||||
_collection = collection;
|
||||
|
||||
if (_subscription != null) _subscription.cancel();
|
||||
_subscription = collection.changes.listen((_) {
|
||||
_updated = true;
|
||||
_ref.requestCheck();
|
||||
});
|
||||
_updated = false;
|
||||
return super.diff(collection);
|
||||
|
||||
// An update has been registered since the last change detection check.
|
||||
// - We reset the flag.
|
||||
// - We diff the collection.
|
||||
} else if (_updated) {
|
||||
_updated = false;
|
||||
return super.diff(collection);
|
||||
|
||||
// No updates has been registered.
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ObservableListDiffFactory implements IterableDifferFactory {
|
||||
const ObservableListDiffFactory();
|
||||
bool supports(obj) => obj is ObservableList;
|
||||
IterableDiffer create(ChangeDetectorRef cdRef) {
|
||||
return new ObservableListDiff(cdRef);
|
||||
}
|
||||
}
|
468
modules/angular2/src/core/dom/browser_adapter.dart
Normal file
468
modules/angular2/src/core/dom/browser_adapter.dart
Normal file
@ -0,0 +1,468 @@
|
||||
library angular.core.facade.dom;
|
||||
|
||||
import 'dart:html';
|
||||
import 'dom_adapter.dart' show setRootDomAdapter;
|
||||
import 'generic_browser_adapter.dart' show GenericBrowserDomAdapter;
|
||||
import '../facade/browser.dart';
|
||||
import 'dart:js' as js;
|
||||
|
||||
// WARNING: Do not expose outside this class. Parsing HTML using this
|
||||
// sanitizer is a security risk.
|
||||
class _IdentitySanitizer implements NodeTreeSanitizer {
|
||||
void sanitizeTree(Node node) {}
|
||||
}
|
||||
|
||||
final _identitySanitizer = new _IdentitySanitizer();
|
||||
|
||||
final _keyCodeToKeyMap = const {
|
||||
8: 'Backspace',
|
||||
9: 'Tab',
|
||||
12: 'Clear',
|
||||
13: 'Enter',
|
||||
16: 'Shift',
|
||||
17: 'Control',
|
||||
18: 'Alt',
|
||||
19: 'Pause',
|
||||
20: 'CapsLock',
|
||||
27: 'Escape',
|
||||
32: ' ',
|
||||
33: 'PageUp',
|
||||
34: 'PageDown',
|
||||
35: 'End',
|
||||
36: 'Home',
|
||||
37: 'ArrowLeft',
|
||||
38: 'ArrowUp',
|
||||
39: 'ArrowRight',
|
||||
40: 'ArrowDown',
|
||||
45: 'Insert',
|
||||
46: 'Delete',
|
||||
65: 'a',
|
||||
66: 'b',
|
||||
67: 'c',
|
||||
68: 'd',
|
||||
69: 'e',
|
||||
70: 'f',
|
||||
71: 'g',
|
||||
72: 'h',
|
||||
73: 'i',
|
||||
74: 'j',
|
||||
75: 'k',
|
||||
76: 'l',
|
||||
77: 'm',
|
||||
78: 'n',
|
||||
79: 'o',
|
||||
80: 'p',
|
||||
81: 'q',
|
||||
82: 'r',
|
||||
83: 's',
|
||||
84: 't',
|
||||
85: 'u',
|
||||
86: 'v',
|
||||
87: 'w',
|
||||
88: 'x',
|
||||
89: 'y',
|
||||
90: 'z',
|
||||
91: 'OS',
|
||||
93: 'ContextMenu',
|
||||
96: '0',
|
||||
97: '1',
|
||||
98: '2',
|
||||
99: '3',
|
||||
100: '4',
|
||||
101: '5',
|
||||
102: '6',
|
||||
103: '7',
|
||||
104: '8',
|
||||
105: '9',
|
||||
106: '*',
|
||||
107: '+',
|
||||
109: '-',
|
||||
110: '.',
|
||||
111: '/',
|
||||
112: 'F1',
|
||||
113: 'F2',
|
||||
114: 'F3',
|
||||
115: 'F4',
|
||||
116: 'F5',
|
||||
117: 'F6',
|
||||
118: 'F7',
|
||||
119: 'F8',
|
||||
120: 'F9',
|
||||
121: 'F10',
|
||||
122: 'F11',
|
||||
123: 'F12',
|
||||
144: 'NumLock',
|
||||
145: 'ScrollLock'
|
||||
};
|
||||
|
||||
final bool _supportsTemplateElement = () {
|
||||
try {
|
||||
return new TemplateElement().content != null;
|
||||
} catch(_) {
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
|
||||
class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||
js.JsFunction _setProperty;
|
||||
js.JsFunction _getProperty;
|
||||
js.JsFunction _hasProperty;
|
||||
Map<String, bool> _hasPropertyCache;
|
||||
BrowserDomAdapter() {
|
||||
_hasPropertyCache = new Map();
|
||||
_setProperty = js.context.callMethod(
|
||||
'eval', ['(function(el, prop, value) { el[prop] = value; })']);
|
||||
_getProperty = js.context
|
||||
.callMethod('eval', ['(function(el, prop) { return el[prop]; })']);
|
||||
_hasProperty = js.context
|
||||
.callMethod('eval', ['(function(el, prop) { return prop in el; })']);
|
||||
}
|
||||
static void makeCurrent() {
|
||||
setRootDomAdapter(new BrowserDomAdapter());
|
||||
}
|
||||
|
||||
bool hasProperty(Element element, String name) {
|
||||
// Always return true as the serverside version html_adapter.dart does so.
|
||||
// TODO: change this once we have schema support.
|
||||
// Note: This nees to kept in sync with html_adapter.dart!
|
||||
return true;
|
||||
}
|
||||
|
||||
void setProperty(Element element, String name, Object value) {
|
||||
var cacheKey = "${element.tagName}.${name}";
|
||||
var hasProperty = this._hasPropertyCache[cacheKey];
|
||||
if (hasProperty == null) {
|
||||
hasProperty = this._hasProperty.apply([element, name]);
|
||||
this._hasPropertyCache[cacheKey] = hasProperty;
|
||||
}
|
||||
if (hasProperty) {
|
||||
_setProperty.apply([element, name, value]);
|
||||
}
|
||||
}
|
||||
|
||||
getProperty(Element element, String name) =>
|
||||
_getProperty.apply([element, name]);
|
||||
|
||||
invoke(Element element, String methodName, List args) =>
|
||||
this.getProperty(element, methodName).apply(args, thisArg: element);
|
||||
|
||||
// TODO(tbosch): move this into a separate environment class once we have it
|
||||
logError(error) {
|
||||
window.console.error(error);
|
||||
}
|
||||
|
||||
log(error) {
|
||||
window.console.log(error);
|
||||
}
|
||||
|
||||
logGroup(error) {
|
||||
window.console.group(error);
|
||||
}
|
||||
|
||||
logGroupEnd() {
|
||||
window.console.groupEnd();
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, String> get attrToPropMap => const <String, String>{
|
||||
'class': 'className',
|
||||
'innerHtml': 'innerHTML',
|
||||
'readonly': 'readOnly',
|
||||
'tabindex': 'tabIndex',
|
||||
};
|
||||
|
||||
Element query(String selector) => document.querySelector(selector);
|
||||
|
||||
Element querySelector(el, String selector) => el.querySelector(selector);
|
||||
|
||||
ElementList querySelectorAll(el, String selector) =>
|
||||
el.querySelectorAll(selector);
|
||||
|
||||
void on(EventTarget element, String event, callback(arg)) {
|
||||
// due to https://code.google.com/p/dart/issues/detail?id=17406
|
||||
// addEventListener misses zones so we use element.on.
|
||||
element.on[event].listen(callback);
|
||||
}
|
||||
|
||||
Function onAndCancel(EventTarget element, String event, callback(arg)) {
|
||||
// due to https://code.google.com/p/dart/issues/detail?id=17406
|
||||
// addEventListener misses zones so we use element.on.
|
||||
var subscription = element.on[event].listen(callback);
|
||||
return subscription.cancel;
|
||||
}
|
||||
|
||||
void dispatchEvent(EventTarget el, Event evt) {
|
||||
el.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
MouseEvent createMouseEvent(String eventType) =>
|
||||
new MouseEvent(eventType, canBubble: true);
|
||||
Event createEvent(String eventType) => new Event(eventType, canBubble: true);
|
||||
void preventDefault(Event evt) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
|
||||
bool isPrevented(Event evt) {
|
||||
return evt.defaultPrevented;
|
||||
}
|
||||
|
||||
String getInnerHTML(Element el) => el.innerHtml;
|
||||
String getOuterHTML(Element el) => el.outerHtml;
|
||||
void setInnerHTML(Element el, String value) {
|
||||
el.innerHtml = value;
|
||||
}
|
||||
|
||||
String nodeName(Node el) => el.nodeName;
|
||||
String nodeValue(Node el) => el.nodeValue;
|
||||
String type(InputElement el) => el.type;
|
||||
Node content(TemplateElement el) => _supportsTemplateElement ? el.content : el;
|
||||
Node firstChild(el) => el.firstChild;
|
||||
Node nextSibling(Node el) => el.nextNode;
|
||||
Element parentElement(Node el) => el.parent;
|
||||
List<Node> childNodes(Node el) => el.childNodes;
|
||||
List childNodesAsList(Node el) => childNodes(el).toList();
|
||||
void clearNodes(Node el) {
|
||||
el.nodes = const [];
|
||||
}
|
||||
|
||||
void appendChild(Node el, Node node) {
|
||||
el.append(node);
|
||||
}
|
||||
|
||||
void removeChild(el, Node node) {
|
||||
node.remove();
|
||||
}
|
||||
|
||||
void replaceChild(Node el, Node newNode, Node oldNode) {
|
||||
oldNode.replaceWith(newNode);
|
||||
}
|
||||
|
||||
ChildNode remove(ChildNode el) {
|
||||
return el..remove();
|
||||
}
|
||||
|
||||
void insertBefore(Node el, node) {
|
||||
el.parentNode.insertBefore(node, el);
|
||||
}
|
||||
|
||||
void insertAllBefore(Node el, Iterable<Node> nodes) {
|
||||
el.parentNode.insertAllBefore(nodes, el);
|
||||
}
|
||||
|
||||
void insertAfter(Node el, Node node) {
|
||||
el.parentNode.insertBefore(node, el.nextNode);
|
||||
}
|
||||
|
||||
String getText(Node el) => el.text;
|
||||
void setText(Node el, String value) {
|
||||
el.text = value;
|
||||
}
|
||||
|
||||
String getValue(el) => el.value;
|
||||
void setValue(el, String value) {
|
||||
el.value = value;
|
||||
}
|
||||
|
||||
bool getChecked(InputElement el) => el.checked;
|
||||
void setChecked(InputElement el, bool isChecked) {
|
||||
el.checked = isChecked;
|
||||
}
|
||||
|
||||
Comment createComment(String text) {
|
||||
return new Comment(text);
|
||||
}
|
||||
|
||||
TemplateElement createTemplate(String html) {
|
||||
var t = new TemplateElement();
|
||||
// We do not sanitize because templates are part of the application code
|
||||
// not user code.
|
||||
t.setInnerHtml(html, treeSanitizer: _identitySanitizer);
|
||||
return t;
|
||||
}
|
||||
|
||||
Element createElement(String tagName, [HtmlDocument doc = null]) {
|
||||
if (doc == null) doc = document;
|
||||
return doc.createElement(tagName);
|
||||
}
|
||||
|
||||
Text createTextNode(String text, [HtmlDocument doc = null]) {
|
||||
return new Text(text);
|
||||
}
|
||||
|
||||
createScriptTag(String attrName, String attrValue,
|
||||
[HtmlDocument doc = null]) {
|
||||
if (doc == null) doc = document;
|
||||
var el = doc.createElement('SCRIPT');
|
||||
el.setAttribute(attrName, attrValue);
|
||||
return el;
|
||||
}
|
||||
|
||||
StyleElement createStyleElement(String css, [HtmlDocument doc = null]) {
|
||||
if (doc == null) doc = document;
|
||||
var el = doc.createElement('STYLE');
|
||||
el.text = css;
|
||||
return el;
|
||||
}
|
||||
|
||||
ShadowRoot createShadowRoot(Element el) => el.createShadowRoot();
|
||||
ShadowRoot getShadowRoot(Element el) => el.shadowRoot;
|
||||
Element getHost(Element el) => (el as ShadowRoot).host;
|
||||
clone(Node node) => node.clone(true);
|
||||
List<Node> getElementsByClassName(Element element, String name) =>
|
||||
element.getElementsByClassName(name);
|
||||
List<Node> getElementsByTagName(Element element, String name) =>
|
||||
element.querySelectorAll(name);
|
||||
List<String> classList(Element element) => element.classes.toList();
|
||||
void addClass(Element element, String classname) {
|
||||
element.classes.add(classname);
|
||||
}
|
||||
|
||||
void removeClass(Element element, String classname) {
|
||||
element.classes.remove(classname);
|
||||
}
|
||||
|
||||
bool hasClass(Element element, String classname) =>
|
||||
element.classes.contains(classname);
|
||||
|
||||
void setStyle(Element element, String stylename, String stylevalue) {
|
||||
element.style.setProperty(stylename, stylevalue);
|
||||
}
|
||||
|
||||
void removeStyle(Element element, String stylename) {
|
||||
element.style.removeProperty(stylename);
|
||||
}
|
||||
|
||||
String getStyle(Element element, String stylename) {
|
||||
return element.style.getPropertyValue(stylename);
|
||||
}
|
||||
|
||||
String tagName(Element element) => element.tagName;
|
||||
|
||||
Map<String, String> attributeMap(Element element) {
|
||||
return new Map.from(element.attributes);
|
||||
}
|
||||
|
||||
bool hasAttribute(Element element, String attribute) =>
|
||||
element.attributes.containsKey(attribute);
|
||||
|
||||
String getAttribute(Element element, String attribute) =>
|
||||
element.getAttribute(attribute);
|
||||
|
||||
void setAttribute(Element element, String name, String value) {
|
||||
element.setAttribute(name, value);
|
||||
}
|
||||
|
||||
void removeAttribute(Element element, String name) {
|
||||
//there is no removeAttribute method as of now in Dart:
|
||||
//https://code.google.com/p/dart/issues/detail?id=19934
|
||||
element.attributes.remove(name);
|
||||
}
|
||||
|
||||
Node templateAwareRoot(Element el) => el is TemplateElement ? el.content : el;
|
||||
|
||||
HtmlDocument createHtmlDocument() =>
|
||||
document.implementation.createHtmlDocument('fakeTitle');
|
||||
|
||||
HtmlDocument defaultDoc() => document;
|
||||
Rectangle getBoundingClientRect(el) => el.getBoundingClientRect();
|
||||
String getTitle() => document.title;
|
||||
void setTitle(String newTitle) {
|
||||
document.title = newTitle;
|
||||
}
|
||||
|
||||
bool elementMatches(n, String selector) =>
|
||||
n is Element && n.matches(selector);
|
||||
bool isTemplateElement(Element el) => el is TemplateElement;
|
||||
bool isTextNode(Node node) => node.nodeType == Node.TEXT_NODE;
|
||||
bool isCommentNode(Node node) => node.nodeType == Node.COMMENT_NODE;
|
||||
bool isElementNode(Node node) => node.nodeType == Node.ELEMENT_NODE;
|
||||
bool hasShadowRoot(Node node) {
|
||||
return node is Element && node.shadowRoot != null;
|
||||
}
|
||||
|
||||
bool isShadowRoot(Node node) {
|
||||
return node is ShadowRoot;
|
||||
}
|
||||
|
||||
Node importIntoDoc(Node node) {
|
||||
return document.importNode(node, true);
|
||||
}
|
||||
|
||||
Node adoptNode(Node node) {
|
||||
return document.adoptNode(node);
|
||||
}
|
||||
|
||||
bool isPageRule(CssRule rule) => rule is CssPageRule;
|
||||
bool isStyleRule(CssRule rule) => rule is CssStyleRule;
|
||||
bool isMediaRule(CssRule rule) => rule is CssMediaRule;
|
||||
bool isKeyframesRule(CssRule rule) => rule is CssKeyframesRule;
|
||||
String getHref(AnchorElement element) {
|
||||
return element.href;
|
||||
}
|
||||
|
||||
String getEventKey(KeyboardEvent event) {
|
||||
int keyCode = event.keyCode;
|
||||
return _keyCodeToKeyMap.containsKey(keyCode)
|
||||
? _keyCodeToKeyMap[keyCode]
|
||||
: 'Unidentified';
|
||||
}
|
||||
|
||||
getGlobalEventTarget(String target) {
|
||||
if (target == "window") {
|
||||
return window;
|
||||
} else if (target == "document") {
|
||||
return document;
|
||||
} else if (target == "body") {
|
||||
return document.body;
|
||||
}
|
||||
}
|
||||
|
||||
getHistory() {
|
||||
return window.history;
|
||||
}
|
||||
|
||||
getLocation() {
|
||||
return window.location;
|
||||
}
|
||||
|
||||
getBaseHref() {
|
||||
var href = getBaseElementHref();
|
||||
if (href == null) {
|
||||
return null;
|
||||
}
|
||||
var baseUri = Uri.parse(href);
|
||||
return baseUri.path[0] == '/' ? baseUri.path : ('/' + baseUri.path);
|
||||
}
|
||||
|
||||
resetBaseElement() {
|
||||
baseElement = null;
|
||||
}
|
||||
|
||||
String getUserAgent() {
|
||||
return window.navigator.userAgent;
|
||||
}
|
||||
|
||||
void setData(Element element, String name, String value) {
|
||||
element.dataset[name] = value;
|
||||
}
|
||||
|
||||
String getData(Element element, String name) {
|
||||
return element.dataset[name];
|
||||
}
|
||||
|
||||
// TODO(tbosch): move this into a separate environment class once we have it
|
||||
setGlobalVar(String name, value) {
|
||||
js.context[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
var baseElement = null;
|
||||
String getBaseElementHref() {
|
||||
if (baseElement == null) {
|
||||
baseElement = document.querySelector('base');
|
||||
if (baseElement == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return baseElement.getAttribute('href');
|
||||
}
|
340
modules/angular2/src/core/dom/browser_adapter.ts
Normal file
340
modules/angular2/src/core/dom/browser_adapter.ts
Normal file
@ -0,0 +1,340 @@
|
||||
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {isBlank, isPresent, global} from 'angular2/src/facade/lang';
|
||||
import {setRootDomAdapter} from './dom_adapter';
|
||||
import {GenericBrowserDomAdapter} from './generic_browser_adapter';
|
||||
|
||||
var _attrToPropMap = {
|
||||
'class': 'className',
|
||||
'innerHtml': 'innerHTML',
|
||||
'readonly': 'readOnly',
|
||||
'tabindex': 'tabIndex'
|
||||
};
|
||||
|
||||
const DOM_KEY_LOCATION_NUMPAD = 3;
|
||||
|
||||
// Map to convert some key or keyIdentifier values to what will be returned by getEventKey
|
||||
var _keyMap = {
|
||||
// The following values are here for cross-browser compatibility and to match the W3C standard
|
||||
// cf http://www.w3.org/TR/DOM-Level-3-Events-key/
|
||||
'\b': 'Backspace',
|
||||
'\t': 'Tab',
|
||||
'\x7F': 'Delete',
|
||||
'\x1B': 'Escape',
|
||||
'Del': 'Delete',
|
||||
'Esc': 'Escape',
|
||||
'Left': 'ArrowLeft',
|
||||
'Right': 'ArrowRight',
|
||||
'Up': 'ArrowUp',
|
||||
'Down': 'ArrowDown',
|
||||
'Menu': 'ContextMenu',
|
||||
'Scroll': 'ScrollLock',
|
||||
'Win': 'OS'
|
||||
};
|
||||
|
||||
// There is a bug in Chrome for numeric keypad keys:
|
||||
// https://code.google.com/p/chromium/issues/detail?id=155654
|
||||
// 1, 2, 3 ... are reported as A, B, C ...
|
||||
var _chromeNumKeyPadMap = {
|
||||
'A': '1',
|
||||
'B': '2',
|
||||
'C': '3',
|
||||
'D': '4',
|
||||
'E': '5',
|
||||
'F': '6',
|
||||
'G': '7',
|
||||
'H': '8',
|
||||
'I': '9',
|
||||
'J': '*',
|
||||
'K': '+',
|
||||
'M': '-',
|
||||
'N': '.',
|
||||
'O': '/',
|
||||
'\x60': '0',
|
||||
'\x90': 'NumLock'
|
||||
};
|
||||
|
||||
/* tslint:disable:requireParameterType */
|
||||
export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||
static makeCurrent() { setRootDomAdapter(new BrowserDomAdapter()); }
|
||||
hasProperty(element, name: string): boolean { return name in element; }
|
||||
setProperty(el: /*element*/ any, name: string, value: any) { el[name] = value; }
|
||||
getProperty(el: /*element*/ any, name: string): any { return el[name]; }
|
||||
invoke(el: /*element*/ any, methodName: string, args: List<any>): any {
|
||||
el[methodName].apply(el, args);
|
||||
}
|
||||
|
||||
// TODO(tbosch): move this into a separate environment class once we have it
|
||||
logError(error) { window.console.error(error); }
|
||||
|
||||
log(error) { window.console.log(error); }
|
||||
|
||||
logGroup(error) {
|
||||
if (window.console.group) {
|
||||
window.console.group(error);
|
||||
} else {
|
||||
window.console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
logGroupEnd() {
|
||||
if (window.console.groupEnd) {
|
||||
window.console.groupEnd();
|
||||
}
|
||||
}
|
||||
|
||||
get attrToPropMap(): any { return _attrToPropMap; }
|
||||
|
||||
query(selector: string): any { return document.querySelector(selector); }
|
||||
querySelector(el, selector: string): HTMLElement { return el.querySelector(selector); }
|
||||
querySelectorAll(el, selector: string): List<any> { return el.querySelectorAll(selector); }
|
||||
on(el, evt, listener) { el.addEventListener(evt, listener, false); }
|
||||
onAndCancel(el, evt, listener): Function {
|
||||
el.addEventListener(evt, listener, false);
|
||||
// Needed to follow Dart's subscription semantic, until fix of
|
||||
// https://code.google.com/p/dart/issues/detail?id=17406
|
||||
return () => { el.removeEventListener(evt, listener, false); };
|
||||
}
|
||||
dispatchEvent(el, evt) { el.dispatchEvent(evt); }
|
||||
createMouseEvent(eventType: string): MouseEvent {
|
||||
var evt: MouseEvent = document.createEvent('MouseEvent');
|
||||
evt.initEvent(eventType, true, true);
|
||||
return evt;
|
||||
}
|
||||
createEvent(eventType): Event {
|
||||
var evt: Event = document.createEvent('Event');
|
||||
evt.initEvent(eventType, true, true);
|
||||
return evt;
|
||||
}
|
||||
preventDefault(evt: Event) {
|
||||
evt.preventDefault();
|
||||
evt.returnValue = false;
|
||||
}
|
||||
isPrevented(evt: Event): boolean {
|
||||
return evt.defaultPrevented || isPresent(evt.returnValue) && !evt.returnValue;
|
||||
}
|
||||
getInnerHTML(el): string { return el.innerHTML; }
|
||||
getOuterHTML(el): string { return el.outerHTML; }
|
||||
nodeName(node: Node): string { return node.nodeName; }
|
||||
nodeValue(node: Node): string { return node.nodeValue; }
|
||||
type(node: HTMLInputElement): string { return node.type; }
|
||||
content(node: Node): Node {
|
||||
if (this.hasProperty(node, "content")) {
|
||||
return (<any>node).content;
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
firstChild(el): Node { return el.firstChild; }
|
||||
nextSibling(el): Node { return el.nextSibling; }
|
||||
parentElement(el): Node { return el.parentNode; }
|
||||
childNodes(el): List<Node> { return el.childNodes; }
|
||||
childNodesAsList(el): List<any> {
|
||||
var childNodes = el.childNodes;
|
||||
var res = ListWrapper.createFixedSize(childNodes.length);
|
||||
for (var i = 0; i < childNodes.length; i++) {
|
||||
res[i] = childNodes[i];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
clearNodes(el) {
|
||||
while (el.firstChild) {
|
||||
el.removeChild(el.firstChild);
|
||||
}
|
||||
}
|
||||
appendChild(el, node) { el.appendChild(node); }
|
||||
removeChild(el, node) { el.removeChild(node); }
|
||||
replaceChild(el: Node, newChild, oldChild) { el.replaceChild(newChild, oldChild); }
|
||||
remove(node): Node {
|
||||
node.parentNode.removeChild(node);
|
||||
return node;
|
||||
}
|
||||
insertBefore(el, node) { el.parentNode.insertBefore(node, el); }
|
||||
insertAllBefore(el, nodes) {
|
||||
ListWrapper.forEach(nodes, (n) => { el.parentNode.insertBefore(n, el); });
|
||||
}
|
||||
insertAfter(el, node) { el.parentNode.insertBefore(node, el.nextSibling); }
|
||||
setInnerHTML(el, value) { el.innerHTML = value; }
|
||||
getText(el): string { return el.textContent; }
|
||||
// TODO(vicb): removed Element type because it does not support StyleElement
|
||||
setText(el, value: string) { el.textContent = value; }
|
||||
getValue(el): string { return el.value; }
|
||||
setValue(el, value: string) { el.value = value; }
|
||||
getChecked(el): boolean { return el.checked; }
|
||||
setChecked(el, value: boolean) { el.checked = value; }
|
||||
createComment(text: string): Comment { return document.createComment(text); }
|
||||
createTemplate(html): HTMLElement {
|
||||
var t = document.createElement('template');
|
||||
t.innerHTML = html;
|
||||
return t;
|
||||
}
|
||||
createElement(tagName, doc = document): HTMLElement { return doc.createElement(tagName); }
|
||||
createTextNode(text: string, doc = document): Text { return doc.createTextNode(text); }
|
||||
createScriptTag(attrName: string, attrValue: string, doc = document): HTMLScriptElement {
|
||||
var el = <HTMLScriptElement>doc.createElement('SCRIPT');
|
||||
el.setAttribute(attrName, attrValue);
|
||||
return el;
|
||||
}
|
||||
createStyleElement(css: string, doc = document): HTMLStyleElement {
|
||||
var style = <HTMLStyleElement>doc.createElement('style');
|
||||
this.appendChild(style, this.createTextNode(css));
|
||||
return style;
|
||||
}
|
||||
createShadowRoot(el: HTMLElement): DocumentFragment { return (<any>el).createShadowRoot(); }
|
||||
getShadowRoot(el: HTMLElement): DocumentFragment { return (<any>el).shadowRoot; }
|
||||
getHost(el: HTMLElement): HTMLElement { return (<any>el).host; }
|
||||
clone(node: Node): Node { return node.cloneNode(true); }
|
||||
getElementsByClassName(element, name: string): List<HTMLElement> {
|
||||
return element.getElementsByClassName(name);
|
||||
}
|
||||
getElementsByTagName(element, name: string): List<HTMLElement> {
|
||||
return element.getElementsByTagName(name);
|
||||
}
|
||||
classList(element): List<any> {
|
||||
return <List<any>>Array.prototype.slice.call(element.classList, 0);
|
||||
}
|
||||
addClass(element, classname: string) { element.classList.add(classname); }
|
||||
removeClass(element, classname: string) { element.classList.remove(classname); }
|
||||
hasClass(element, classname: string): boolean { return element.classList.contains(classname); }
|
||||
setStyle(element, stylename: string, stylevalue: string) {
|
||||
element.style[stylename] = stylevalue;
|
||||
}
|
||||
removeStyle(element, stylename: string) { element.style[stylename] = null; }
|
||||
getStyle(element, stylename: string): string { return element.style[stylename]; }
|
||||
tagName(element): string { return element.tagName; }
|
||||
attributeMap(element): Map<string, string> {
|
||||
var res = new Map();
|
||||
var elAttrs = element.attributes;
|
||||
for (var i = 0; i < elAttrs.length; i++) {
|
||||
var attrib = elAttrs[i];
|
||||
res.set(attrib.name, attrib.value);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
hasAttribute(element, attribute: string): boolean { return element.hasAttribute(attribute); }
|
||||
getAttribute(element, attribute: string): string { return element.getAttribute(attribute); }
|
||||
setAttribute(element, name: string, value: string) { element.setAttribute(name, value); }
|
||||
removeAttribute(element, attribute: string) { element.removeAttribute(attribute); }
|
||||
templateAwareRoot(el): any { return this.isTemplateElement(el) ? this.content(el) : el; }
|
||||
createHtmlDocument(): HTMLDocument {
|
||||
return document.implementation.createHTMLDocument('fakeTitle');
|
||||
}
|
||||
defaultDoc(): HTMLDocument { return document; }
|
||||
getBoundingClientRect(el): any {
|
||||
try {
|
||||
return el.getBoundingClientRect();
|
||||
} catch (e) {
|
||||
return {top: 0, bottom: 0, left: 0, right: 0, width: 0, height: 0};
|
||||
}
|
||||
}
|
||||
getTitle(): string { return document.title; }
|
||||
setTitle(newTitle: string) { document.title = newTitle || ''; }
|
||||
elementMatches(n, selector: string): boolean {
|
||||
var matches = false;
|
||||
if (n instanceof HTMLElement) {
|
||||
if (n.matches) {
|
||||
matches = n.matches(selector);
|
||||
} else if (n.msMatchesSelector) {
|
||||
matches = n.msMatchesSelector(selector);
|
||||
} else if (n.webkitMatchesSelector) {
|
||||
matches = n.webkitMatchesSelector(selector);
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
isTemplateElement(el: any): boolean {
|
||||
return el instanceof HTMLElement && el.nodeName == "TEMPLATE";
|
||||
}
|
||||
isTextNode(node: Node): boolean { return node.nodeType === Node.TEXT_NODE; }
|
||||
isCommentNode(node: Node): boolean { return node.nodeType === Node.COMMENT_NODE; }
|
||||
isElementNode(node: Node): boolean { return node.nodeType === Node.ELEMENT_NODE; }
|
||||
hasShadowRoot(node): boolean { return node instanceof HTMLElement && isPresent(node.shadowRoot); }
|
||||
isShadowRoot(node): boolean { return node instanceof DocumentFragment; }
|
||||
importIntoDoc(node: Node): any {
|
||||
var toImport = node;
|
||||
if (this.isTemplateElement(node)) {
|
||||
toImport = this.content(node);
|
||||
}
|
||||
return document.importNode(toImport, true);
|
||||
}
|
||||
adoptNode(node: Node): any { return document.adoptNode(node); }
|
||||
isPageRule(rule): boolean { return rule.type === CSSRule.PAGE_RULE; }
|
||||
isStyleRule(rule): boolean { return rule.type === CSSRule.STYLE_RULE; }
|
||||
isMediaRule(rule): boolean { return rule.type === CSSRule.MEDIA_RULE; }
|
||||
isKeyframesRule(rule): boolean { return rule.type === CSSRule.KEYFRAMES_RULE; }
|
||||
getHref(el: Element): string { return (<any>el).href; }
|
||||
getEventKey(event): string {
|
||||
var key = event.key;
|
||||
if (isBlank(key)) {
|
||||
key = event.keyIdentifier;
|
||||
// keyIdentifier is defined in the old draft of DOM Level 3 Events implemented by Chrome and
|
||||
// Safari
|
||||
// cf
|
||||
// http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/events.html#Events-KeyboardEvents-Interfaces
|
||||
if (isBlank(key)) {
|
||||
return 'Unidentified';
|
||||
}
|
||||
if (key.startsWith('U+')) {
|
||||
key = String.fromCharCode(parseInt(key.substring(2), 16));
|
||||
if (event.location === DOM_KEY_LOCATION_NUMPAD && _chromeNumKeyPadMap.hasOwnProperty(key)) {
|
||||
// There is a bug in Chrome for numeric keypad keys:
|
||||
// https://code.google.com/p/chromium/issues/detail?id=155654
|
||||
// 1, 2, 3 ... are reported as A, B, C ...
|
||||
key = _chromeNumKeyPadMap[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_keyMap.hasOwnProperty(key)) {
|
||||
key = _keyMap[key];
|
||||
}
|
||||
return key;
|
||||
}
|
||||
getGlobalEventTarget(target: string): EventTarget {
|
||||
if (target == "window") {
|
||||
return window;
|
||||
} else if (target == "document") {
|
||||
return document;
|
||||
} else if (target == "body") {
|
||||
return document.body;
|
||||
}
|
||||
}
|
||||
getHistory(): History { return window.history; }
|
||||
getLocation(): Location { return window.location; }
|
||||
getBaseHref(): string {
|
||||
var href = getBaseElementHref();
|
||||
if (isBlank(href)) {
|
||||
return null;
|
||||
}
|
||||
return relativePath(href);
|
||||
}
|
||||
resetBaseElement(): void { baseElement = null; }
|
||||
getUserAgent(): string { return window.navigator.userAgent; }
|
||||
setData(element, name: string, value: string) {
|
||||
this.setAttribute(element, 'data-' + name, value);
|
||||
}
|
||||
getData(element, name: string): string { return this.getAttribute(element, 'data-' + name); }
|
||||
// TODO(tbosch): move this into a separate environment class once we have it
|
||||
setGlobalVar(name: string, value: any) { global[name] = value; }
|
||||
}
|
||||
|
||||
|
||||
var baseElement = null;
|
||||
function getBaseElementHref(): string {
|
||||
if (isBlank(baseElement)) {
|
||||
baseElement = document.querySelector('base');
|
||||
if (isBlank(baseElement)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return baseElement.getAttribute('href');
|
||||
}
|
||||
|
||||
// based on urlUtils.js in AngularJS 1
|
||||
var urlParsingNode = null;
|
||||
function relativePath(url): string {
|
||||
if (isBlank(urlParsingNode)) {
|
||||
urlParsingNode = document.createElement("a");
|
||||
}
|
||||
urlParsingNode.setAttribute('href', url);
|
||||
return (urlParsingNode.pathname.charAt(0) === '/') ? urlParsingNode.pathname :
|
||||
'/' + urlParsingNode.pathname;
|
||||
}
|
135
modules/angular2/src/core/dom/dom_adapter.ts
Normal file
135
modules/angular2/src/core/dom/dom_adapter.ts
Normal file
@ -0,0 +1,135 @@
|
||||
import {BaseException, isBlank} from 'angular2/src/facade/lang';
|
||||
|
||||
export var DOM: DomAdapter;
|
||||
|
||||
export function setRootDomAdapter(adapter: DomAdapter) {
|
||||
if (isBlank(DOM)) {
|
||||
DOM = adapter;
|
||||
}
|
||||
}
|
||||
|
||||
function _abstract() {
|
||||
return new BaseException('This method is abstract');
|
||||
}
|
||||
|
||||
/* tslint:disable:requireParameterType */
|
||||
/**
|
||||
* Provides DOM operations in an environment-agnostic way.
|
||||
*/
|
||||
export class DomAdapter {
|
||||
hasProperty(element, name: string): boolean { throw _abstract(); }
|
||||
setProperty(el: Element, name: string, value: any) { throw _abstract(); }
|
||||
getProperty(el: Element, name: string): any { throw _abstract(); }
|
||||
invoke(el: Element, methodName: string, args: List<any>): any { throw _abstract(); }
|
||||
|
||||
logError(error) { throw _abstract(); }
|
||||
log(error) { throw _abstract(); }
|
||||
logGroup(error) { throw _abstract(); }
|
||||
logGroupEnd() { throw _abstract(); }
|
||||
|
||||
/**
|
||||
* Maps attribute names to their corresponding property names for cases
|
||||
* where attribute name doesn't match property name.
|
||||
*/
|
||||
get attrToPropMap(): StringMap<string, string> { throw _abstract(); }
|
||||
|
||||
parse(templateHtml: string) { throw _abstract(); }
|
||||
query(selector: string): any { throw _abstract(); }
|
||||
querySelector(el, selector: string): HTMLElement { throw _abstract(); }
|
||||
querySelectorAll(el, selector: string): List<any> { throw _abstract(); }
|
||||
on(el, evt, listener) { throw _abstract(); }
|
||||
onAndCancel(el, evt, listener): Function { throw _abstract(); }
|
||||
dispatchEvent(el, evt) { throw _abstract(); }
|
||||
createMouseEvent(eventType): any { throw _abstract(); }
|
||||
createEvent(eventType: string): any { throw _abstract(); }
|
||||
preventDefault(evt) { throw _abstract(); }
|
||||
isPrevented(evt): boolean { throw _abstract(); }
|
||||
getInnerHTML(el): string { throw _abstract(); }
|
||||
getOuterHTML(el): string { throw _abstract(); }
|
||||
nodeName(node): string { throw _abstract(); }
|
||||
nodeValue(node): string { throw _abstract(); }
|
||||
type(node): string { throw _abstract(); }
|
||||
content(node): any { throw _abstract(); }
|
||||
firstChild(el): Node { throw _abstract(); }
|
||||
nextSibling(el): Node { throw _abstract(); }
|
||||
parentElement(el): Node { throw _abstract(); }
|
||||
childNodes(el): List<Node> { throw _abstract(); }
|
||||
childNodesAsList(el): List<Node> { throw _abstract(); }
|
||||
clearNodes(el) { throw _abstract(); }
|
||||
appendChild(el, node) { throw _abstract(); }
|
||||
removeChild(el, node) { throw _abstract(); }
|
||||
replaceChild(el, newNode, oldNode) { throw _abstract(); }
|
||||
remove(el): Node { throw _abstract(); }
|
||||
insertBefore(el, node) { throw _abstract(); }
|
||||
insertAllBefore(el, nodes) { throw _abstract(); }
|
||||
insertAfter(el, node) { throw _abstract(); }
|
||||
setInnerHTML(el, value) { throw _abstract(); }
|
||||
getText(el): string { throw _abstract(); }
|
||||
setText(el, value: string) { throw _abstract(); }
|
||||
getValue(el): string { throw _abstract(); }
|
||||
setValue(el, value: string) { throw _abstract(); }
|
||||
getChecked(el): boolean { throw _abstract(); }
|
||||
setChecked(el, value: boolean) { throw _abstract(); }
|
||||
createComment(text: string): any { throw _abstract(); }
|
||||
createTemplate(html): HTMLElement { throw _abstract(); }
|
||||
createElement(tagName, doc = null): HTMLElement { throw _abstract(); }
|
||||
createTextNode(text: string, doc = null): Text { throw _abstract(); }
|
||||
createScriptTag(attrName: string, attrValue: string, doc = null): HTMLElement {
|
||||
throw _abstract();
|
||||
}
|
||||
createStyleElement(css: string, doc = null): HTMLStyleElement { throw _abstract(); }
|
||||
createShadowRoot(el): any { throw _abstract(); }
|
||||
getShadowRoot(el): any { throw _abstract(); }
|
||||
getHost(el): any { throw _abstract(); }
|
||||
getDistributedNodes(el): List<Node> { throw _abstract(); }
|
||||
clone /*<T extends Node>*/ (node: Node /*T*/): Node /*T*/ { throw _abstract(); }
|
||||
getElementsByClassName(element, name: string): List<HTMLElement> { throw _abstract(); }
|
||||
getElementsByTagName(element, name: string): List<HTMLElement> { throw _abstract(); }
|
||||
classList(element): List<any> { throw _abstract(); }
|
||||
addClass(element, classname: string) { throw _abstract(); }
|
||||
removeClass(element, classname: string) { throw _abstract(); }
|
||||
hasClass(element, classname: string): boolean { throw _abstract(); }
|
||||
setStyle(element, stylename: string, stylevalue: string) { throw _abstract(); }
|
||||
removeStyle(element, stylename: string) { throw _abstract(); }
|
||||
getStyle(element, stylename: string): string { throw _abstract(); }
|
||||
tagName(element): string { throw _abstract(); }
|
||||
attributeMap(element): Map<string, string> { throw _abstract(); }
|
||||
hasAttribute(element, attribute: string): boolean { throw _abstract(); }
|
||||
getAttribute(element, attribute: string): string { throw _abstract(); }
|
||||
setAttribute(element, name: string, value: string) { throw _abstract(); }
|
||||
removeAttribute(element, attribute: string) { throw _abstract(); }
|
||||
templateAwareRoot(el) { throw _abstract(); }
|
||||
createHtmlDocument(): HTMLDocument { throw _abstract(); }
|
||||
defaultDoc(): HTMLDocument { throw _abstract(); }
|
||||
getBoundingClientRect(el) { throw _abstract(); }
|
||||
getTitle(): string { throw _abstract(); }
|
||||
setTitle(newTitle: string) { throw _abstract(); }
|
||||
elementMatches(n, selector: string): boolean { throw _abstract(); }
|
||||
isTemplateElement(el: any): boolean { throw _abstract(); }
|
||||
isTextNode(node): boolean { throw _abstract(); }
|
||||
isCommentNode(node): boolean { throw _abstract(); }
|
||||
isElementNode(node): boolean { throw _abstract(); }
|
||||
hasShadowRoot(node): boolean { throw _abstract(); }
|
||||
isShadowRoot(node): boolean { throw _abstract(); }
|
||||
importIntoDoc /*<T extends Node>*/ (node: Node /*T*/): Node /*T*/ { throw _abstract(); }
|
||||
adoptNode /*<T extends Node>*/ (node: Node /*T*/): Node /*T*/ { throw _abstract(); }
|
||||
isPageRule(rule): boolean { throw _abstract(); }
|
||||
isStyleRule(rule): boolean { throw _abstract(); }
|
||||
isMediaRule(rule): boolean { throw _abstract(); }
|
||||
isKeyframesRule(rule): boolean { throw _abstract(); }
|
||||
getHref(element): string { throw _abstract(); }
|
||||
getEventKey(event): string { throw _abstract(); }
|
||||
resolveAndSetHref(element, baseUrl: string, href: string) { throw _abstract(); }
|
||||
cssToRules(css: string): List<any> { throw _abstract(); }
|
||||
supportsDOMEvents(): boolean { throw _abstract(); }
|
||||
supportsNativeShadowDOM(): boolean { throw _abstract(); }
|
||||
getGlobalEventTarget(target: string): any { throw _abstract(); }
|
||||
getHistory(): History { throw _abstract(); }
|
||||
getLocation(): Location { throw _abstract(); }
|
||||
getBaseHref(): string { throw _abstract(); }
|
||||
resetBaseElement(): void { throw _abstract(); }
|
||||
getUserAgent(): string { throw _abstract(); }
|
||||
setData(element, name: string, value: string) { throw _abstract(); }
|
||||
getData(element, name: string): string { throw _abstract(); }
|
||||
setGlobalVar(name: string, value: any) { throw _abstract(); }
|
||||
}
|
40
modules/angular2/src/core/dom/generic_browser_adapter.ts
Normal file
40
modules/angular2/src/core/dom/generic_browser_adapter.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {isPresent, isFunction} from 'angular2/src/facade/lang';
|
||||
import {DomAdapter} from './dom_adapter';
|
||||
|
||||
/**
|
||||
* Provides DOM operations in any browser environment.
|
||||
*/
|
||||
export class GenericBrowserDomAdapter extends DomAdapter {
|
||||
getDistributedNodes(el: HTMLElement): List<Node> { return (<any>el).getDistributedNodes(); }
|
||||
resolveAndSetHref(el: HTMLAnchorElement, baseUrl: string, href: string) {
|
||||
el.href = href == null ? baseUrl : baseUrl + '/../' + href;
|
||||
}
|
||||
cssToRules(css: string): List<any> {
|
||||
var style = this.createStyleElement(css);
|
||||
this.appendChild(this.defaultDoc().head, style);
|
||||
var rules = [];
|
||||
if (isPresent(style.sheet)) {
|
||||
// TODO(sorvell): Firefox throws when accessing the rules of a stylesheet
|
||||
// with an @import
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=625013
|
||||
try {
|
||||
var rawRules = (<any>style.sheet).cssRules;
|
||||
rules = ListWrapper.createFixedSize(rawRules.length);
|
||||
for (var i = 0; i < rawRules.length; i++) {
|
||||
rules[i] = rawRules[i];
|
||||
}
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
} else {
|
||||
// console.warn('sheet not found', style);
|
||||
}
|
||||
this.remove(style);
|
||||
return rules;
|
||||
}
|
||||
supportsDOMEvents(): boolean { return true; }
|
||||
supportsNativeShadowDOM(): boolean {
|
||||
return isFunction((<any>this.defaultDoc().body).createShadowRoot);
|
||||
}
|
||||
}
|
423
modules/angular2/src/core/dom/html_adapter.dart
Normal file
423
modules/angular2/src/core/dom/html_adapter.dart
Normal file
@ -0,0 +1,423 @@
|
||||
library angular2.dom.htmlAdapter;
|
||||
|
||||
import 'dom_adapter.dart';
|
||||
import 'package:html/parser.dart' as parser;
|
||||
import 'package:html/dom.dart';
|
||||
import 'dart:io';
|
||||
|
||||
class Html5LibDomAdapter implements DomAdapter {
|
||||
static void makeCurrent() {
|
||||
setRootDomAdapter(new Html5LibDomAdapter());
|
||||
}
|
||||
|
||||
hasProperty(element, String name) {
|
||||
// This is needed for serverside compile to generate the right getters/setters.
|
||||
// TODO: change this once we have property schema support.
|
||||
// Attention: Keep this in sync with browser_adapter.dart!
|
||||
return true;
|
||||
}
|
||||
|
||||
void setProperty(Element element, String name, Object value) =>
|
||||
throw 'not implemented';
|
||||
|
||||
getProperty(Element element, String name) => throw 'not implemented';
|
||||
|
||||
invoke(Element element, String methodName, List args) =>
|
||||
throw 'not implemented';
|
||||
|
||||
logError(error) {
|
||||
stderr.writeln('${error}');
|
||||
}
|
||||
|
||||
log(error) {
|
||||
stdout.writeln('${error}');
|
||||
}
|
||||
|
||||
logGroup(error) {
|
||||
stdout.writeln('${error}');
|
||||
}
|
||||
|
||||
logGroupEnd() {}
|
||||
|
||||
@override
|
||||
final attrToPropMap = const {
|
||||
'innerHtml': 'innerHTML',
|
||||
'readonly': 'readOnly',
|
||||
'tabindex': 'tabIndex',
|
||||
};
|
||||
|
||||
@override
|
||||
getGlobalEventTarget(String target) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
@override
|
||||
getTitle() {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
@override
|
||||
setTitle(String newTitle) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
@override
|
||||
String getEventKey(event) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
@override
|
||||
void replaceChild(el, newNode, oldNode) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic getBoundingClientRect(el) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
Element parse(String templateHtml) => parser.parse(templateHtml).firstChild;
|
||||
query(selector) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
querySelector(el, String selector) {
|
||||
return el.querySelector(selector);
|
||||
}
|
||||
|
||||
List querySelectorAll(el, String selector) {
|
||||
return el.querySelectorAll(selector);
|
||||
}
|
||||
|
||||
on(el, evt, listener) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
Function onAndCancel(el, evt, listener) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
dispatchEvent(el, evt) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
createMouseEvent(eventType) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
createEvent(eventType) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
preventDefault(evt) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
isPrevented(evt) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
getInnerHTML(el) {
|
||||
return el.innerHtml;
|
||||
}
|
||||
|
||||
getOuterHTML(el) {
|
||||
return el.outerHtml;
|
||||
}
|
||||
|
||||
String nodeName(node) {
|
||||
switch (node.nodeType) {
|
||||
case Node.ELEMENT_NODE:
|
||||
return (node as Element).localName;
|
||||
case Node.TEXT_NODE:
|
||||
return '#text';
|
||||
default:
|
||||
throw 'not implemented for type ${node.nodeType}. '
|
||||
'See http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1950641247'
|
||||
' for node types definitions.';
|
||||
}
|
||||
}
|
||||
|
||||
String nodeValue(node) => node.data;
|
||||
String type(node) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
content(node) {
|
||||
return node;
|
||||
}
|
||||
|
||||
firstChild(el) => el is NodeList ? el.first : el.firstChild;
|
||||
|
||||
nextSibling(el) {
|
||||
final parentNode = el.parentNode;
|
||||
if (parentNode == null) return null;
|
||||
final siblings = parentNode.nodes;
|
||||
final index = siblings.indexOf(el);
|
||||
if (index < siblings.length - 1) {
|
||||
return siblings[index + 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
parentElement(el) {
|
||||
return el.parent;
|
||||
}
|
||||
|
||||
List childNodes(el) => el.nodes;
|
||||
List childNodesAsList(el) => el.nodes;
|
||||
clearNodes(el) {
|
||||
el.nodes.forEach((e) => e.remove());
|
||||
}
|
||||
|
||||
appendChild(el, node) => el.append(node.remove());
|
||||
removeChild(el, node) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
remove(el) => el.remove();
|
||||
insertBefore(el, node) {
|
||||
if (el.parent == null) throw '$el must have a parent';
|
||||
el.parent.insertBefore(node, el);
|
||||
}
|
||||
|
||||
insertAllBefore(el, nodes) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
insertAfter(el, node) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
setInnerHTML(el, value) {
|
||||
el.innerHtml = value;
|
||||
}
|
||||
|
||||
getText(el) {
|
||||
return el.text;
|
||||
}
|
||||
|
||||
setText(el, String value) => el.text = value;
|
||||
|
||||
getValue(el) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
setValue(el, String value) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
getChecked(el) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
setChecked(el, bool value) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
createComment(String text) => new Comment(text);
|
||||
createTemplate(String html) => createElement('template')..innerHtml = html;
|
||||
createElement(tagName, [doc]) {
|
||||
return new Element.tag(tagName);
|
||||
}
|
||||
|
||||
createTextNode(String text, [doc]) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
createScriptTag(String attrName, String attrValue, [doc]) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
createStyleElement(String css, [doc]) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
createShadowRoot(el) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
getShadowRoot(el) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
getHost(el) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
clone(node) => node.clone(true);
|
||||
getElementsByClassName(element, String name) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
getElementsByTagName(element, String name) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
List classList(element) => element.classes.toList();
|
||||
|
||||
addClass(element, String classname) {
|
||||
element.classes.add(classname);
|
||||
}
|
||||
|
||||
removeClass(element, String classname) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
hasClass(element, String classname) => element.classes.contains(classname);
|
||||
|
||||
setStyle(element, String stylename, String stylevalue) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
removeStyle(element, String stylename) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
getStyle(element, String stylename) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
String tagName(element) => element.localName;
|
||||
|
||||
attributeMap(element) {
|
||||
// `attributes` keys can be {@link AttributeName}s.
|
||||
var map = <String, String>{};
|
||||
element.attributes.forEach((key, value) {
|
||||
map['$key'] = value;
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
hasAttribute(element, String attribute) {
|
||||
// `attributes` keys can be {@link AttributeName}s.
|
||||
return element.attributes.keys.any((key) => '$key' == attribute);
|
||||
}
|
||||
|
||||
getAttribute(element, String attribute) {
|
||||
// `attributes` keys can be {@link AttributeName}s.
|
||||
var key = element.attributes.keys.firstWhere((key) => '$key' == attribute,
|
||||
orElse: () {});
|
||||
return element.attributes[key];
|
||||
}
|
||||
|
||||
setAttribute(element, String name, String value) {
|
||||
element.attributes[name] = value;
|
||||
}
|
||||
|
||||
removeAttribute(element, String attribute) {
|
||||
element.attributes.remove(attribute);
|
||||
}
|
||||
|
||||
templateAwareRoot(el) => el;
|
||||
|
||||
createHtmlDocument() {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
defaultDoc() {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
bool elementMatches(n, String selector) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
bool isTemplateElement(Element el) {
|
||||
return el != null && el.localName.toLowerCase() == 'template';
|
||||
}
|
||||
|
||||
bool isTextNode(node) => node.nodeType == Node.TEXT_NODE;
|
||||
bool isCommentNode(node) => node.nodeType == Node.COMMENT_NODE;
|
||||
|
||||
bool isElementNode(node) => node.nodeType == Node.ELEMENT_NODE;
|
||||
|
||||
bool hasShadowRoot(node) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
bool isShadowRoot(node) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
importIntoDoc(node) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
adoptNode(node) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
bool isPageRule(rule) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
bool isStyleRule(rule) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
bool isMediaRule(rule) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
bool isKeyframesRule(rule) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
String getHref(element) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
void resolveAndSetHref(element, baseUrl, href) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
List cssToRules(String css) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
List getDistributedNodes(Node) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
bool supportsDOMEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool supportsNativeShadowDOM() {
|
||||
return false;
|
||||
}
|
||||
|
||||
getHistory() {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
getLocation() {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
getBaseHref() {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
resetBaseElement() {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
String getUserAgent() {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
void setData(Element element, String name, String value) {
|
||||
this.setAttribute(element, 'data-${name}', value);
|
||||
}
|
||||
|
||||
String getData(Element element, String name) {
|
||||
return this.getAttribute(element, 'data-${name}');
|
||||
}
|
||||
|
||||
// TODO(tbosch): move this into a separate environment class once we have it
|
||||
setGlobalVar(String name, value) {
|
||||
// noop on the server
|
||||
}
|
||||
}
|
3
modules/angular2/src/core/dom/parse5_adapter.dart
Normal file
3
modules/angular2/src/core/dom/parse5_adapter.dart
Normal file
@ -0,0 +1,3 @@
|
||||
library angular2.src.dom.parse5_adapter;
|
||||
|
||||
// no dart implementation
|
730
modules/angular2/src/core/dom/parse5_adapter.ts
Normal file
730
modules/angular2/src/core/dom/parse5_adapter.ts
Normal file
@ -0,0 +1,730 @@
|
||||
var parse5 = require('parse5');
|
||||
var parser = new parse5.Parser(parse5.TreeAdapters.htmlparser2);
|
||||
var serializer = new parse5.Serializer(parse5.TreeAdapters.htmlparser2);
|
||||
var treeAdapter = parser.treeAdapter;
|
||||
|
||||
var cssParse = require('css').parse;
|
||||
|
||||
var url = require('url');
|
||||
|
||||
import {List, MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {DomAdapter, setRootDomAdapter} from './dom_adapter';
|
||||
import {BaseException, isPresent, isBlank, global} from 'angular2/src/facade/lang';
|
||||
import {SelectorMatcher, CssSelector} from 'angular2/src/render/dom/compiler/selector';
|
||||
|
||||
var _attrToPropMap = {
|
||||
'class': 'className',
|
||||
'innerHtml': 'innerHTML',
|
||||
'readonly': 'readOnly',
|
||||
'tabindex': 'tabIndex',
|
||||
};
|
||||
var defDoc = null;
|
||||
|
||||
var mapProps = ['attribs', 'x-attribsNamespace', 'x-attribsPrefix'];
|
||||
|
||||
function _notImplemented(methodName) {
|
||||
return new BaseException('This method is not implemented in Parse5DomAdapter: ' + methodName);
|
||||
}
|
||||
|
||||
/* tslint:disable:requireParameterType */
|
||||
export class Parse5DomAdapter extends DomAdapter {
|
||||
static makeCurrent() { setRootDomAdapter(new Parse5DomAdapter()); }
|
||||
|
||||
hasProperty(element, name: string): boolean {
|
||||
return _HTMLElementPropertyList.indexOf(name) > -1;
|
||||
}
|
||||
// TODO(tbosch): don't even call this method when we run the tests on server side
|
||||
// by not using the DomRenderer in tests. Keeping this for now to make tests happy...
|
||||
setProperty(el: /*element*/ any, name: string, value: any) {
|
||||
if (name === 'innerHTML') {
|
||||
this.setInnerHTML(el, value);
|
||||
} else if (name === 'className') {
|
||||
el.attribs["class"] = el.className = value;
|
||||
} else {
|
||||
el[name] = value;
|
||||
}
|
||||
}
|
||||
// TODO(tbosch): don't even call this method when we run the tests on server side
|
||||
// by not using the DomRenderer in tests. Keeping this for now to make tests happy...
|
||||
getProperty(el: /*element*/ any, name: string): any { return el[name]; }
|
||||
|
||||
logError(error) { console.error(error); }
|
||||
|
||||
log(error) { console.log(error); }
|
||||
|
||||
logGroup(error) { console.log(error); }
|
||||
|
||||
logGroupEnd() {}
|
||||
|
||||
get attrToPropMap() { return _attrToPropMap; }
|
||||
|
||||
query(selector) { throw _notImplemented('query'); }
|
||||
querySelector(el, selector: string): any { return this.querySelectorAll(el, selector)[0]; }
|
||||
querySelectorAll(el, selector: string): List<any> {
|
||||
var res = [];
|
||||
var _recursive = (result, node, selector, matcher) => {
|
||||
var cNodes = node.childNodes;
|
||||
if (cNodes && cNodes.length > 0) {
|
||||
for (var i = 0; i < cNodes.length; i++) {
|
||||
var childNode = cNodes[i];
|
||||
if (this.elementMatches(childNode, selector, matcher)) {
|
||||
result.push(childNode);
|
||||
}
|
||||
_recursive(result, childNode, selector, matcher);
|
||||
}
|
||||
}
|
||||
};
|
||||
var matcher = new SelectorMatcher();
|
||||
matcher.addSelectables(CssSelector.parse(selector));
|
||||
_recursive(res, el, selector, matcher);
|
||||
return res;
|
||||
}
|
||||
elementMatches(node, selector: string, matcher = null): boolean {
|
||||
if (this.isElementNode(node) && selector === '*') {
|
||||
return true;
|
||||
}
|
||||
var result = false;
|
||||
if (selector && selector.charAt(0) == "#") {
|
||||
result = this.getAttribute(node, 'id') == selector.substring(1);
|
||||
} else if (selector) {
|
||||
var result = false;
|
||||
if (matcher == null) {
|
||||
matcher = new SelectorMatcher();
|
||||
matcher.addSelectables(CssSelector.parse(selector));
|
||||
}
|
||||
|
||||
var cssSelector = new CssSelector();
|
||||
cssSelector.setElement(this.tagName(node));
|
||||
if (node.attribs) {
|
||||
for (var attrName in node.attribs) {
|
||||
cssSelector.addAttribute(attrName, node.attribs[attrName]);
|
||||
}
|
||||
}
|
||||
var classList = this.classList(node);
|
||||
for (var i = 0; i < classList.length; i++) {
|
||||
cssSelector.addClassName(classList[i]);
|
||||
}
|
||||
|
||||
matcher.match(cssSelector, function(selector, cb) { result = true; });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
on(el, evt, listener) {
|
||||
var listenersMap: StringMap<any, any> = el._eventListenersMap;
|
||||
if (isBlank(listenersMap)) {
|
||||
var listenersMap: StringMap<any, any> = StringMapWrapper.create();
|
||||
el._eventListenersMap = listenersMap;
|
||||
}
|
||||
var listeners = StringMapWrapper.get(listenersMap, evt);
|
||||
if (isBlank(listeners)) {
|
||||
listeners = [];
|
||||
}
|
||||
listeners.push(listener);
|
||||
StringMapWrapper.set(listenersMap, evt, listeners);
|
||||
}
|
||||
onAndCancel(el, evt, listener): Function {
|
||||
this.on(el, evt, listener);
|
||||
return () => {
|
||||
ListWrapper.remove(StringMapWrapper.get<List<any>>(el._eventListenersMap, evt), listener);
|
||||
};
|
||||
}
|
||||
dispatchEvent(el, evt) {
|
||||
if (isBlank(evt.target)) {
|
||||
evt.target = el;
|
||||
}
|
||||
if (isPresent(el._eventListenersMap)) {
|
||||
var listeners: any = StringMapWrapper.get(el._eventListenersMap, evt.type);
|
||||
if (isPresent(listeners)) {
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
listeners[i](evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isPresent(el.parent)) {
|
||||
this.dispatchEvent(el.parent, evt);
|
||||
}
|
||||
if (isPresent(el._window)) {
|
||||
this.dispatchEvent(el._window, evt);
|
||||
}
|
||||
}
|
||||
createMouseEvent(eventType): Event { return this.createEvent(eventType); }
|
||||
createEvent(eventType: string): Event {
|
||||
var evt = <Event>{
|
||||
type: eventType,
|
||||
defaultPrevented: false,
|
||||
preventDefault: () => { evt.defaultPrevented = true; }
|
||||
};
|
||||
return evt;
|
||||
}
|
||||
preventDefault(evt) { evt.returnValue = false; }
|
||||
isPrevented(evt): boolean { return isPresent(evt.returnValue) && !evt.returnValue; }
|
||||
getInnerHTML(el): string { return serializer.serialize(this.templateAwareRoot(el)); }
|
||||
getOuterHTML(el): string {
|
||||
serializer.html = '';
|
||||
serializer._serializeElement(el);
|
||||
return serializer.html;
|
||||
}
|
||||
nodeName(node): string { return node.tagName; }
|
||||
nodeValue(node): string { return node.nodeValue; }
|
||||
type(node: any): string { throw _notImplemented('type'); }
|
||||
content(node): string { return node.childNodes[0]; }
|
||||
firstChild(el): Node { return el.firstChild; }
|
||||
nextSibling(el): Node { return el.nextSibling; }
|
||||
parentElement(el): Node { return el.parent; }
|
||||
childNodes(el): Node[] { return el.childNodes; }
|
||||
childNodesAsList(el): List<any> {
|
||||
var childNodes = el.childNodes;
|
||||
var res = ListWrapper.createFixedSize(childNodes.length);
|
||||
for (var i = 0; i < childNodes.length; i++) {
|
||||
res[i] = childNodes[i];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
clearNodes(el) {
|
||||
while (el.childNodes.length > 0) {
|
||||
this.remove(el.childNodes[0]);
|
||||
}
|
||||
}
|
||||
appendChild(el, node) {
|
||||
this.remove(node);
|
||||
treeAdapter.appendChild(this.templateAwareRoot(el), node);
|
||||
}
|
||||
removeChild(el, node) {
|
||||
if (ListWrapper.contains(el.childNodes, node)) {
|
||||
this.remove(node);
|
||||
}
|
||||
}
|
||||
remove(el): HTMLElement {
|
||||
var parent = el.parent;
|
||||
if (parent) {
|
||||
var index = parent.childNodes.indexOf(el);
|
||||
parent.childNodes.splice(index, 1);
|
||||
}
|
||||
var prev = el.previousSibling;
|
||||
var next = el.nextSibling;
|
||||
if (prev) {
|
||||
prev.next = next;
|
||||
}
|
||||
if (next) {
|
||||
next.prev = prev;
|
||||
}
|
||||
el.prev = null;
|
||||
el.next = null;
|
||||
el.parent = null;
|
||||
return el;
|
||||
}
|
||||
insertBefore(el, node) {
|
||||
this.remove(node);
|
||||
treeAdapter.insertBefore(el.parent, node, el);
|
||||
}
|
||||
insertAllBefore(el, nodes) {
|
||||
ListWrapper.forEach(nodes, (n) => { this.insertBefore(el, n); });
|
||||
}
|
||||
insertAfter(el, node) {
|
||||
if (el.nextSibling) {
|
||||
this.insertBefore(el.nextSibling, node);
|
||||
} else {
|
||||
this.appendChild(el.parent, node);
|
||||
}
|
||||
}
|
||||
setInnerHTML(el, value) {
|
||||
this.clearNodes(el);
|
||||
var content = parser.parseFragment(value);
|
||||
for (var i = 0; i < content.childNodes.length; i++) {
|
||||
treeAdapter.appendChild(el, content.childNodes[i]);
|
||||
}
|
||||
}
|
||||
getText(el): string {
|
||||
if (this.isTextNode(el)) {
|
||||
return el.data;
|
||||
} else if (isBlank(el.childNodes) || el.childNodes.length == 0) {
|
||||
return "";
|
||||
} else {
|
||||
var textContent = "";
|
||||
for (var i = 0; i < el.childNodes.length; i++) {
|
||||
textContent += this.getText(el.childNodes[i]);
|
||||
}
|
||||
return textContent;
|
||||
}
|
||||
}
|
||||
setText(el, value: string) {
|
||||
if (this.isTextNode(el)) {
|
||||
el.data = value;
|
||||
} else {
|
||||
this.clearNodes(el);
|
||||
if (value !== '') treeAdapter.insertText(el, value);
|
||||
}
|
||||
}
|
||||
getValue(el): string { return el.value; }
|
||||
setValue(el, value: string) { el.value = value; }
|
||||
getChecked(el): boolean { return el.checked; }
|
||||
setChecked(el, value: boolean) { el.checked = value; }
|
||||
createComment(text: string): Comment { return treeAdapter.createCommentNode(text); }
|
||||
createTemplate(html): HTMLElement {
|
||||
var template = treeAdapter.createElement("template", 'http://www.w3.org/1999/xhtml', []);
|
||||
var content = parser.parseFragment(html);
|
||||
treeAdapter.appendChild(template, content);
|
||||
return template;
|
||||
}
|
||||
createElement(tagName): HTMLElement {
|
||||
return treeAdapter.createElement(tagName, 'http://www.w3.org/1999/xhtml', []);
|
||||
}
|
||||
createTextNode(text: string): Text { throw _notImplemented('createTextNode'); }
|
||||
createScriptTag(attrName: string, attrValue: string): HTMLElement {
|
||||
return treeAdapter.createElement("script", 'http://www.w3.org/1999/xhtml',
|
||||
[{name: attrName, value: attrValue}]);
|
||||
}
|
||||
createStyleElement(css: string): HTMLStyleElement {
|
||||
var style = this.createElement('style');
|
||||
this.setText(style, css);
|
||||
return <HTMLStyleElement>style;
|
||||
}
|
||||
createShadowRoot(el): HTMLElement {
|
||||
el.shadowRoot = treeAdapter.createDocumentFragment();
|
||||
el.shadowRoot.parent = el;
|
||||
return el.shadowRoot;
|
||||
}
|
||||
getShadowRoot(el): Element { return el.shadowRoot; }
|
||||
getHost(el): string { return el.host; }
|
||||
getDistributedNodes(el: any): List<Node> { throw _notImplemented('getDistributedNodes'); }
|
||||
clone(node: Node): Node {
|
||||
var _recursive = (node) => {
|
||||
var nodeClone = Object.create(Object.getPrototypeOf(node));
|
||||
for (var prop in node) {
|
||||
var desc = Object.getOwnPropertyDescriptor(node, prop);
|
||||
if (desc && 'value' in desc && typeof desc.value !== 'object') {
|
||||
nodeClone[prop] = node[prop];
|
||||
}
|
||||
}
|
||||
nodeClone.parent = null;
|
||||
nodeClone.prev = null;
|
||||
nodeClone.next = null;
|
||||
nodeClone.children = null;
|
||||
|
||||
mapProps.forEach(mapName => {
|
||||
if (isPresent(node[mapName])) {
|
||||
nodeClone[mapName] = {};
|
||||
for (var prop in node[mapName]) {
|
||||
nodeClone[mapName][prop] = node[mapName][prop];
|
||||
}
|
||||
}
|
||||
});
|
||||
var cNodes = node.children;
|
||||
if (cNodes) {
|
||||
var cNodesClone = new Array(cNodes.length);
|
||||
for (var i = 0; i < cNodes.length; i++) {
|
||||
var childNode = cNodes[i];
|
||||
var childNodeClone = _recursive(childNode);
|
||||
cNodesClone[i] = childNodeClone;
|
||||
if (i > 0) {
|
||||
childNodeClone.prev = cNodesClone[i - 1];
|
||||
cNodesClone[i - 1].next = childNodeClone;
|
||||
}
|
||||
childNodeClone.parent = nodeClone;
|
||||
}
|
||||
nodeClone.children = cNodesClone;
|
||||
}
|
||||
return nodeClone;
|
||||
};
|
||||
return _recursive(node);
|
||||
}
|
||||
getElementsByClassName(element, name: string): List<HTMLElement> {
|
||||
return this.querySelectorAll(element, "." + name);
|
||||
}
|
||||
getElementsByTagName(element: any, name: string): List<HTMLElement> {
|
||||
throw _notImplemented('getElementsByTagName');
|
||||
}
|
||||
classList(element): List<string> {
|
||||
var classAttrValue = null;
|
||||
var attributes = element.attribs;
|
||||
if (attributes && attributes.hasOwnProperty("class")) {
|
||||
classAttrValue = attributes["class"];
|
||||
}
|
||||
return classAttrValue ? classAttrValue.trim().split(/\s+/g) : [];
|
||||
}
|
||||
addClass(element, classname: string) {
|
||||
var classList = this.classList(element);
|
||||
var index = classList.indexOf(classname);
|
||||
if (index == -1) {
|
||||
classList.push(classname);
|
||||
element.attribs["class"] = element.className = ListWrapper.join(classList, " ");
|
||||
}
|
||||
}
|
||||
removeClass(element, classname: string) {
|
||||
var classList = this.classList(element);
|
||||
var index = classList.indexOf(classname);
|
||||
if (index > -1) {
|
||||
classList.splice(index, 1);
|
||||
element.attribs["class"] = element.className = ListWrapper.join(classList, " ");
|
||||
}
|
||||
}
|
||||
hasClass(element, classname: string): boolean {
|
||||
return ListWrapper.contains(this.classList(element), classname);
|
||||
}
|
||||
_readStyleAttribute(element) {
|
||||
var styleMap = {};
|
||||
var attributes = element.attribs;
|
||||
if (attributes && attributes.hasOwnProperty("style")) {
|
||||
var styleAttrValue = attributes["style"];
|
||||
var styleList = styleAttrValue.split(/;+/g);
|
||||
for (var i = 0; i < styleList.length; i++) {
|
||||
if (styleList[i].length > 0) {
|
||||
var elems = styleList[i].split(/:+/g);
|
||||
styleMap[elems[0].trim()] = elems[1].trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
return styleMap;
|
||||
}
|
||||
_writeStyleAttribute(element, styleMap) {
|
||||
var styleAttrValue = "";
|
||||
for (var key in styleMap) {
|
||||
var newValue = styleMap[key];
|
||||
if (newValue && newValue.length > 0) {
|
||||
styleAttrValue += key + ":" + styleMap[key] + ";";
|
||||
}
|
||||
}
|
||||
element.attribs["style"] = styleAttrValue;
|
||||
}
|
||||
setStyle(element, stylename: string, stylevalue: string) {
|
||||
var styleMap = this._readStyleAttribute(element);
|
||||
styleMap[stylename] = stylevalue;
|
||||
this._writeStyleAttribute(element, styleMap);
|
||||
}
|
||||
removeStyle(element, stylename: string) { this.setStyle(element, stylename, null); }
|
||||
getStyle(element, stylename: string): string {
|
||||
var styleMap = this._readStyleAttribute(element);
|
||||
return styleMap.hasOwnProperty(stylename) ? styleMap[stylename] : "";
|
||||
}
|
||||
tagName(element): string { return element.tagName == "style" ? "STYLE" : element.tagName; }
|
||||
attributeMap(element): Map<string, string> {
|
||||
var res = new Map();
|
||||
var elAttrs = treeAdapter.getAttrList(element);
|
||||
for (var i = 0; i < elAttrs.length; i++) {
|
||||
var attrib = elAttrs[i];
|
||||
res.set(attrib.name, attrib.value);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
hasAttribute(element, attribute: string): boolean {
|
||||
return element.attribs && element.attribs.hasOwnProperty(attribute);
|
||||
}
|
||||
getAttribute(element, attribute: string): string {
|
||||
return element.attribs && element.attribs.hasOwnProperty(attribute) ?
|
||||
element.attribs[attribute] :
|
||||
null;
|
||||
}
|
||||
setAttribute(element, attribute: string, value: string) {
|
||||
if (attribute) {
|
||||
element.attribs[attribute] = value;
|
||||
}
|
||||
}
|
||||
removeAttribute(element, attribute: string) {
|
||||
if (attribute) {
|
||||
StringMapWrapper.delete(element.attribs, attribute);
|
||||
}
|
||||
}
|
||||
templateAwareRoot(el): any { return this.isTemplateElement(el) ? this.content(el) : el; }
|
||||
createHtmlDocument(): Document {
|
||||
var newDoc = treeAdapter.createDocument();
|
||||
newDoc.title = "fake title";
|
||||
var head = treeAdapter.createElement("head", null, []);
|
||||
var body = treeAdapter.createElement("body", 'http://www.w3.org/1999/xhtml', []);
|
||||
this.appendChild(newDoc, head);
|
||||
this.appendChild(newDoc, body);
|
||||
StringMapWrapper.set(newDoc, "head", head);
|
||||
StringMapWrapper.set(newDoc, "body", body);
|
||||
StringMapWrapper.set(newDoc, "_window", StringMapWrapper.create());
|
||||
return newDoc;
|
||||
}
|
||||
defaultDoc(): Document {
|
||||
if (defDoc === null) {
|
||||
defDoc = this.createHtmlDocument();
|
||||
}
|
||||
return defDoc;
|
||||
}
|
||||
getBoundingClientRect(el): any { return {left: 0, top: 0, width: 0, height: 0}; }
|
||||
getTitle(): string { return this.defaultDoc().title || ""; }
|
||||
setTitle(newTitle: string) { this.defaultDoc().title = newTitle; }
|
||||
isTemplateElement(el: any): boolean {
|
||||
return this.isElementNode(el) && this.tagName(el) === "template";
|
||||
}
|
||||
isTextNode(node): boolean { return treeAdapter.isTextNode(node); }
|
||||
isCommentNode(node): boolean { return treeAdapter.isCommentNode(node); }
|
||||
isElementNode(node): boolean { return node ? treeAdapter.isElementNode(node) : false; }
|
||||
hasShadowRoot(node): boolean { return isPresent(node.shadowRoot); }
|
||||
isShadowRoot(node): boolean { return this.getShadowRoot(node) == node; }
|
||||
importIntoDoc(node): any { return this.clone(node); }
|
||||
adoptNode(node): any { return node; }
|
||||
isPageRule(rule): boolean {
|
||||
return rule.type === 6; // CSSRule.PAGE_RULE
|
||||
}
|
||||
isStyleRule(rule): boolean {
|
||||
return rule.type === 1; // CSSRule.MEDIA_RULE
|
||||
}
|
||||
isMediaRule(rule): boolean {
|
||||
return rule.type === 4; // CSSRule.MEDIA_RULE
|
||||
}
|
||||
isKeyframesRule(rule): boolean {
|
||||
return rule.type === 7; // CSSRule.KEYFRAMES_RULE
|
||||
}
|
||||
getHref(el): string { return el.href; }
|
||||
resolveAndSetHref(el, baseUrl: string, href: string) {
|
||||
if (href == null) {
|
||||
el.href = baseUrl;
|
||||
} else {
|
||||
el.href = url.resolve(baseUrl, href);
|
||||
}
|
||||
}
|
||||
_buildRules(parsedRules, css?) {
|
||||
var rules = [];
|
||||
for (var i = 0; i < parsedRules.length; i++) {
|
||||
var parsedRule = parsedRules[i];
|
||||
var rule: StringMap<string, any> = StringMapWrapper.create();
|
||||
StringMapWrapper.set(rule, "cssText", css);
|
||||
StringMapWrapper.set(rule, "style", {content: "", cssText: ""});
|
||||
if (parsedRule.type == "rule") {
|
||||
StringMapWrapper.set(rule, "type", 1);
|
||||
StringMapWrapper.set(rule, "selectorText", parsedRule.selectors.join(", ")
|
||||
.replace(/\s{2,}/g, " ")
|
||||
.replace(/\s*~\s*/g, " ~ ")
|
||||
.replace(/\s*\+\s*/g, " + ")
|
||||
.replace(/\s*>\s*/g, " > ")
|
||||
.replace(/\[(\w+)=(\w+)\]/g, '[$1="$2"]'));
|
||||
if (isBlank(parsedRule.declarations)) {
|
||||
continue;
|
||||
}
|
||||
for (var j = 0; j < parsedRule.declarations.length; j++) {
|
||||
var declaration = parsedRule.declarations[j];
|
||||
StringMapWrapper.set(StringMapWrapper.get(rule, "style"), declaration.property,
|
||||
declaration.value);
|
||||
StringMapWrapper.get(rule, "style").cssText +=
|
||||
declaration.property + ": " + declaration.value + ";";
|
||||
}
|
||||
} else if (parsedRule.type == "media") {
|
||||
StringMapWrapper.set(rule, "type", 4);
|
||||
StringMapWrapper.set(rule, "media", {mediaText: parsedRule.media});
|
||||
if (parsedRule.rules) {
|
||||
StringMapWrapper.set(rule, "cssRules", this._buildRules(parsedRule.rules));
|
||||
}
|
||||
}
|
||||
rules.push(rule);
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
cssToRules(css: string): List<any> {
|
||||
css = css.replace(/url\(\'(.+)\'\)/g, 'url($1)');
|
||||
var rules = [];
|
||||
var parsedCSS = cssParse(css, {silent: true});
|
||||
if (parsedCSS.stylesheet && parsedCSS.stylesheet.rules) {
|
||||
rules = this._buildRules(parsedCSS.stylesheet.rules, css);
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
supportsDOMEvents(): boolean { return false; }
|
||||
supportsNativeShadowDOM(): boolean { return false; }
|
||||
getGlobalEventTarget(target: string): any {
|
||||
if (target == "window") {
|
||||
return (<any>this.defaultDoc())._window;
|
||||
} else if (target == "document") {
|
||||
return this.defaultDoc();
|
||||
} else if (target == "body") {
|
||||
return this.defaultDoc().body;
|
||||
}
|
||||
}
|
||||
getBaseHref(): string { throw 'not implemented'; }
|
||||
resetBaseElement(): void { throw 'not implemented'; }
|
||||
getHistory(): History { throw 'not implemented'; }
|
||||
getLocation(): Location { throw 'not implemented'; }
|
||||
getUserAgent(): string { return "Fake user agent"; }
|
||||
getData(el, name: string): string { return this.getAttribute(el, 'data-' + name); }
|
||||
setData(el, name: string, value: string) { this.setAttribute(el, 'data-' + name, value); }
|
||||
// TODO(tbosch): move this into a separate environment class once we have it
|
||||
setGlobalVar(name: string, value: any) { global[name] = value; }
|
||||
}
|
||||
|
||||
// TODO: build a proper list, this one is all the keys of a HTMLInputElement
|
||||
var _HTMLElementPropertyList = [
|
||||
"webkitEntries",
|
||||
"incremental",
|
||||
"webkitdirectory",
|
||||
"selectionDirection",
|
||||
"selectionEnd",
|
||||
"selectionStart",
|
||||
"labels",
|
||||
"validationMessage",
|
||||
"validity",
|
||||
"willValidate",
|
||||
"width",
|
||||
"valueAsNumber",
|
||||
"valueAsDate",
|
||||
"value",
|
||||
"useMap",
|
||||
"defaultValue",
|
||||
"type",
|
||||
"step",
|
||||
"src",
|
||||
"size",
|
||||
"required",
|
||||
"readOnly",
|
||||
"placeholder",
|
||||
"pattern",
|
||||
"name",
|
||||
"multiple",
|
||||
"min",
|
||||
"minLength",
|
||||
"maxLength",
|
||||
"max",
|
||||
"list",
|
||||
"indeterminate",
|
||||
"height",
|
||||
"formTarget",
|
||||
"formNoValidate",
|
||||
"formMethod",
|
||||
"formEnctype",
|
||||
"formAction",
|
||||
"files",
|
||||
"form",
|
||||
"disabled",
|
||||
"dirName",
|
||||
"checked",
|
||||
"defaultChecked",
|
||||
"autofocus",
|
||||
"autocomplete",
|
||||
"alt",
|
||||
"align",
|
||||
"accept",
|
||||
"onautocompleteerror",
|
||||
"onautocomplete",
|
||||
"onwaiting",
|
||||
"onvolumechange",
|
||||
"ontoggle",
|
||||
"ontimeupdate",
|
||||
"onsuspend",
|
||||
"onsubmit",
|
||||
"onstalled",
|
||||
"onshow",
|
||||
"onselect",
|
||||
"onseeking",
|
||||
"onseeked",
|
||||
"onscroll",
|
||||
"onresize",
|
||||
"onreset",
|
||||
"onratechange",
|
||||
"onprogress",
|
||||
"onplaying",
|
||||
"onplay",
|
||||
"onpause",
|
||||
"onmousewheel",
|
||||
"onmouseup",
|
||||
"onmouseover",
|
||||
"onmouseout",
|
||||
"onmousemove",
|
||||
"onmouseleave",
|
||||
"onmouseenter",
|
||||
"onmousedown",
|
||||
"onloadstart",
|
||||
"onloadedmetadata",
|
||||
"onloadeddata",
|
||||
"onload",
|
||||
"onkeyup",
|
||||
"onkeypress",
|
||||
"onkeydown",
|
||||
"oninvalid",
|
||||
"oninput",
|
||||
"onfocus",
|
||||
"onerror",
|
||||
"onended",
|
||||
"onemptied",
|
||||
"ondurationchange",
|
||||
"ondrop",
|
||||
"ondragstart",
|
||||
"ondragover",
|
||||
"ondragleave",
|
||||
"ondragenter",
|
||||
"ondragend",
|
||||
"ondrag",
|
||||
"ondblclick",
|
||||
"oncuechange",
|
||||
"oncontextmenu",
|
||||
"onclose",
|
||||
"onclick",
|
||||
"onchange",
|
||||
"oncanplaythrough",
|
||||
"oncanplay",
|
||||
"oncancel",
|
||||
"onblur",
|
||||
"onabort",
|
||||
"spellcheck",
|
||||
"isContentEditable",
|
||||
"contentEditable",
|
||||
"outerText",
|
||||
"innerText",
|
||||
"accessKey",
|
||||
"hidden",
|
||||
"webkitdropzone",
|
||||
"draggable",
|
||||
"tabIndex",
|
||||
"dir",
|
||||
"translate",
|
||||
"lang",
|
||||
"title",
|
||||
"childElementCount",
|
||||
"lastElementChild",
|
||||
"firstElementChild",
|
||||
"children",
|
||||
"onwebkitfullscreenerror",
|
||||
"onwebkitfullscreenchange",
|
||||
"nextElementSibling",
|
||||
"previousElementSibling",
|
||||
"onwheel",
|
||||
"onselectstart",
|
||||
"onsearch",
|
||||
"onpaste",
|
||||
"oncut",
|
||||
"oncopy",
|
||||
"onbeforepaste",
|
||||
"onbeforecut",
|
||||
"onbeforecopy",
|
||||
"shadowRoot",
|
||||
"dataset",
|
||||
"classList",
|
||||
"className",
|
||||
"outerHTML",
|
||||
"innerHTML",
|
||||
"scrollHeight",
|
||||
"scrollWidth",
|
||||
"scrollTop",
|
||||
"scrollLeft",
|
||||
"clientHeight",
|
||||
"clientWidth",
|
||||
"clientTop",
|
||||
"clientLeft",
|
||||
"offsetParent",
|
||||
"offsetHeight",
|
||||
"offsetWidth",
|
||||
"offsetTop",
|
||||
"offsetLeft",
|
||||
"localName",
|
||||
"prefix",
|
||||
"namespaceURI",
|
||||
"id",
|
||||
"style",
|
||||
"attributes",
|
||||
"tagName",
|
||||
"parentElement",
|
||||
"textContent",
|
||||
"baseURI",
|
||||
"ownerDocument",
|
||||
"nextSibling",
|
||||
"previousSibling",
|
||||
"lastChild",
|
||||
"firstChild",
|
||||
"childNodes",
|
||||
"parentNode",
|
||||
"nodeType",
|
||||
"nodeValue",
|
||||
"nodeName",
|
||||
"closure_lm_714617",
|
||||
"__jsaction"
|
||||
];
|
126
modules/angular2/src/core/facade/async.dart
Normal file
126
modules/angular2/src/core/facade/async.dart
Normal file
@ -0,0 +1,126 @@
|
||||
library angular2.core.facade.async;
|
||||
|
||||
import 'dart:async';
|
||||
export 'dart:async' show Future, Stream, StreamController, StreamSubscription;
|
||||
|
||||
class PromiseWrapper {
|
||||
static Future resolve(obj) => new Future.value(obj);
|
||||
|
||||
static Future reject(obj, stackTrace) => new Future.error(obj,
|
||||
stackTrace != null ? stackTrace : obj is Error ? obj.stackTrace : null);
|
||||
|
||||
static Future<List> all(List<dynamic> promises) {
|
||||
return Future
|
||||
.wait(promises.map((p) => p is Future ? p : new Future.value(p)));
|
||||
}
|
||||
|
||||
static Future then(Future promise, success(value), [Function onError]) {
|
||||
if (success == null) return promise.catchError(onError);
|
||||
return promise.then(success, onError: onError);
|
||||
}
|
||||
|
||||
static Future wrap(Function fn) {
|
||||
return new Future(fn);
|
||||
}
|
||||
|
||||
// Note: We can't rename this method to `catch`, as this is not a valid
|
||||
// method name in Dart.
|
||||
static Future catchError(Future promise, Function onError) {
|
||||
return promise.catchError(onError);
|
||||
}
|
||||
|
||||
static PromiseCompleter<dynamic> completer() =>
|
||||
new PromiseCompleter(new Completer());
|
||||
}
|
||||
|
||||
class TimerWrapper {
|
||||
static Timer setTimeout(fn(), int millis) =>
|
||||
new Timer(new Duration(milliseconds: millis), fn);
|
||||
static void clearTimeout(Timer timer) {
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
static Timer setInterval(fn(), int millis) {
|
||||
var interval = new Duration(milliseconds: millis);
|
||||
return new Timer.periodic(interval, (Timer timer) {
|
||||
fn();
|
||||
});
|
||||
}
|
||||
|
||||
static void clearInterval(Timer timer) {
|
||||
timer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
class ObservableWrapper {
|
||||
static StreamSubscription subscribe(Stream s, Function onNext,
|
||||
[onError, onComplete]) {
|
||||
return s.listen(onNext,
|
||||
onError: onError, onDone: onComplete, cancelOnError: true);
|
||||
}
|
||||
|
||||
static bool isObservable(obs) {
|
||||
return obs is Stream;
|
||||
}
|
||||
|
||||
static void dispose(StreamSubscription s) {
|
||||
s.cancel();
|
||||
}
|
||||
|
||||
static void callNext(EventEmitter emitter, value) {
|
||||
emitter.add(value);
|
||||
}
|
||||
|
||||
static void callThrow(EventEmitter emitter, error) {
|
||||
emitter.addError(error);
|
||||
}
|
||||
|
||||
static void callReturn(EventEmitter emitter) {
|
||||
emitter.close();
|
||||
}
|
||||
}
|
||||
|
||||
class EventEmitter extends Stream {
|
||||
StreamController<dynamic> _controller;
|
||||
|
||||
EventEmitter() {
|
||||
_controller = new StreamController.broadcast();
|
||||
}
|
||||
|
||||
StreamSubscription listen(void onData(dynamic line),
|
||||
{void onError(Error error), void onDone(), bool cancelOnError}) {
|
||||
return _controller.stream.listen(onData,
|
||||
onError: onError, onDone: onDone, cancelOnError: cancelOnError);
|
||||
}
|
||||
|
||||
void add(value) {
|
||||
_controller.add(value);
|
||||
}
|
||||
|
||||
void addError(error) {
|
||||
_controller.addError(error);
|
||||
}
|
||||
|
||||
void close() {
|
||||
_controller.close();
|
||||
}
|
||||
}
|
||||
|
||||
class PromiseCompleter<T> {
|
||||
final Completer<T> c;
|
||||
|
||||
PromiseCompleter(this.c);
|
||||
|
||||
Future get promise => c.future;
|
||||
|
||||
void resolve(v) {
|
||||
c.complete(v);
|
||||
}
|
||||
|
||||
void reject(error, stack) {
|
||||
if (stack == null && error is Error) {
|
||||
stack = error.stackTrace;
|
||||
}
|
||||
c.completeError(error, stack);
|
||||
}
|
||||
}
|
124
modules/angular2/src/core/facade/async.ts
Normal file
124
modules/angular2/src/core/facade/async.ts
Normal file
@ -0,0 +1,124 @@
|
||||
/// <reference path="../../typings/rx/rx.d.ts" />
|
||||
|
||||
import {global, isPresent} from 'angular2/src/facade/lang';
|
||||
import {List} from 'angular2/src/facade/collection';
|
||||
import * as Rx from 'rx';
|
||||
|
||||
export {Promise};
|
||||
|
||||
export interface PromiseCompleter<R> {
|
||||
promise: Promise<R>;
|
||||
resolve: (value?: R | PromiseLike<R>) => void;
|
||||
reject: (error?: any, stackTrace?: string) => void;
|
||||
}
|
||||
|
||||
export class PromiseWrapper {
|
||||
static resolve<T>(obj: T): Promise<T> { return Promise.resolve(obj); }
|
||||
|
||||
static reject(obj: any, _): Promise<any> { return Promise.reject(obj); }
|
||||
|
||||
// Note: We can't rename this method into `catch`, as this is not a valid
|
||||
// method name in Dart.
|
||||
static catchError<T>(promise: Promise<T>,
|
||||
onError: (error: any) => T | PromiseLike<T>): Promise<T> {
|
||||
return promise.catch(onError);
|
||||
}
|
||||
|
||||
static all(promises: List<any>): Promise<any> {
|
||||
if (promises.length == 0) return Promise.resolve([]);
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
static then<T, U>(promise: Promise<T>, success: (value: T) => U | PromiseLike<U>,
|
||||
rejection?: (error: any, stack?: any) => U | PromiseLike<U>): Promise<U> {
|
||||
return promise.then(success, rejection);
|
||||
}
|
||||
|
||||
static wrap<T>(computation: () => T): Promise<T> {
|
||||
return new Promise((res, rej) => {
|
||||
try {
|
||||
res(computation());
|
||||
} catch (e) {
|
||||
rej(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static completer(): PromiseCompleter<any> {
|
||||
var resolve;
|
||||
var reject;
|
||||
|
||||
var p = new Promise(function(res, rej) {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
|
||||
return {promise: p, resolve: resolve, reject: reject};
|
||||
}
|
||||
}
|
||||
|
||||
export class TimerWrapper {
|
||||
static setTimeout(fn: Function, millis: number): number { return global.setTimeout(fn, millis); }
|
||||
static clearTimeout(id: number): void { global.clearTimeout(id); }
|
||||
|
||||
static setInterval(fn: Function, millis: number): number {
|
||||
return global.setInterval(fn, millis);
|
||||
}
|
||||
static clearInterval(id: number): void { global.clearInterval(id); }
|
||||
}
|
||||
|
||||
export class ObservableWrapper {
|
||||
// TODO(vsavkin): when we use rxnext, try inferring the generic type from the first arg
|
||||
static subscribe<T>(emitter: Observable, onNext: (value: T) => void,
|
||||
onThrow: (exception: any) => void = null,
|
||||
onReturn: () => void = null): Object {
|
||||
return emitter.observer({next: onNext, throw: onThrow, return: onReturn});
|
||||
}
|
||||
|
||||
static isObservable(obs: any): boolean { return obs instanceof Observable; }
|
||||
|
||||
static dispose(subscription: any) { subscription.dispose(); }
|
||||
|
||||
static callNext(emitter: EventEmitter, value: any) { emitter.next(value); }
|
||||
|
||||
static callThrow(emitter: EventEmitter, error: any) { emitter.throw(error); }
|
||||
|
||||
static callReturn(emitter: EventEmitter) { emitter.return (null); }
|
||||
}
|
||||
|
||||
// TODO: vsavkin change to interface
|
||||
export class Observable {
|
||||
observer(generator: any): Object { return null; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Use Rx.Observable but provides an adapter to make it work as specified here:
|
||||
* https://github.com/jhusain/observable-spec
|
||||
*
|
||||
* Once a reference implementation of the spec is available, switch to it.
|
||||
*/
|
||||
export class EventEmitter extends Observable {
|
||||
_subject: Rx.Subject<any>;
|
||||
_immediateScheduler;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._subject = new Rx.Subject<any>();
|
||||
this._immediateScheduler = (<any>Rx.Scheduler).immediate;
|
||||
}
|
||||
|
||||
observer(generator: any): Rx.IDisposable {
|
||||
return this._subject.observeOn(this._immediateScheduler)
|
||||
.subscribe((value) => { setTimeout(() => generator.next(value)); },
|
||||
(error) => generator.throw ? generator.throw(error) : null,
|
||||
() => generator.return ? generator.return () : null);
|
||||
}
|
||||
|
||||
toRx(): Rx.Observable<any> { return this._subject; }
|
||||
|
||||
next(value: any) { this._subject.onNext(value); }
|
||||
|
||||
throw(error: any) { this._subject.onError(error); }
|
||||
|
||||
return (value?: any) { this._subject.onCompleted(); }
|
||||
}
|
30
modules/angular2/src/core/facade/browser.dart
Normal file
30
modules/angular2/src/core/facade/browser.dart
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Dart version of browser APIs. This library depends on 'dart:html' and
|
||||
* therefore can only run in the browser.
|
||||
*/
|
||||
library angular2.src.facade.browser;
|
||||
|
||||
import 'dart:js' show context;
|
||||
|
||||
export 'dart:html'
|
||||
show
|
||||
document,
|
||||
location,
|
||||
window,
|
||||
Element,
|
||||
Node,
|
||||
MouseEvent,
|
||||
KeyboardEvent,
|
||||
Event,
|
||||
EventTarget,
|
||||
History,
|
||||
Location,
|
||||
EventListener;
|
||||
|
||||
final _gc = context['gc'];
|
||||
|
||||
void gc() {
|
||||
if (_gc != null) {
|
||||
_gc.apply(const []);
|
||||
}
|
||||
}
|
16
modules/angular2/src/core/facade/browser.ts
Normal file
16
modules/angular2/src/core/facade/browser.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* JS version of browser APIs. This library can only run in the browser.
|
||||
*/
|
||||
var win = window;
|
||||
|
||||
export {win as window};
|
||||
export var document = window.document;
|
||||
export var location = window.location;
|
||||
export var gc = window['gc'] ? () => window['gc']() : () => null;
|
||||
export const Event = Event;
|
||||
export const MouseEvent = MouseEvent;
|
||||
export const KeyboardEvent = KeyboardEvent;
|
||||
export const EventTarget = EventTarget;
|
||||
export const History = History;
|
||||
export const Location = Location;
|
||||
export const EventListener = EventListener;
|
264
modules/angular2/src/core/facade/collection.dart
Normal file
264
modules/angular2/src/core/facade/collection.dart
Normal file
@ -0,0 +1,264 @@
|
||||
library facade.collection;
|
||||
|
||||
import 'dart:collection' show IterableBase, Iterator;
|
||||
import 'dart:convert' show JsonEncoder;
|
||||
export 'dart:core' show Map, List, Set;
|
||||
import 'dart:math' show max, min;
|
||||
|
||||
var jsonEncoder = new JsonEncoder();
|
||||
|
||||
class MapIterator extends Iterator<List> {
|
||||
final Iterator _iterator;
|
||||
final Map _map;
|
||||
|
||||
MapIterator(Map map)
|
||||
: _map = map,
|
||||
_iterator = map.keys.iterator;
|
||||
|
||||
bool moveNext() => _iterator.moveNext();
|
||||
|
||||
List get current {
|
||||
return _iterator.current != null
|
||||
? [_iterator.current, _map[_iterator.current]]
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
class IterableMap extends IterableBase<List> {
|
||||
final Map _map;
|
||||
|
||||
IterableMap(Map map) : _map = map;
|
||||
|
||||
Iterator<List> get iterator => new MapIterator(_map);
|
||||
}
|
||||
|
||||
class MapWrapper {
|
||||
static Map clone(Map m) => new Map.from(m);
|
||||
|
||||
// in opposite to JS, Dart does not create a new map
|
||||
static Map createFromStringMap(Map m) => m;
|
||||
|
||||
// in opposite to JS, Dart does not create a new map
|
||||
static Map toStringMap(Map m) => m;
|
||||
|
||||
static Map createFromPairs(List pairs) => pairs.fold({}, (m, p) {
|
||||
m[p[0]] = p[1];
|
||||
return m;
|
||||
});
|
||||
static forEach(Map m, fn(v, k)) {
|
||||
m.forEach((k, v) => fn(v, k));
|
||||
}
|
||||
|
||||
static get(Map map, key) => map[key];
|
||||
static int size(Map m) => m.length;
|
||||
static void delete(Map m, k) {
|
||||
m.remove(k);
|
||||
}
|
||||
|
||||
static void clearValues(Map m) {
|
||||
for (var k in m.keys) {
|
||||
m[k] = null;
|
||||
}
|
||||
}
|
||||
|
||||
static Iterable iterable(Map m) => new IterableMap(m);
|
||||
static List keys(Map m) => m.keys.toList();
|
||||
static List values(Map m) => m.values.toList();
|
||||
}
|
||||
|
||||
class StringMapWrapper {
|
||||
static Map create() => {};
|
||||
static bool contains(Map map, key) => map.containsKey(key);
|
||||
static get(Map map, key) => map[key];
|
||||
static void set(Map map, key, value) {
|
||||
map[key] = value;
|
||||
}
|
||||
|
||||
static void delete(Map m, k) {
|
||||
m.remove(k);
|
||||
}
|
||||
|
||||
static void forEach(Map m, fn(v, k)) {
|
||||
m.forEach((k, v) => fn(v, k));
|
||||
}
|
||||
|
||||
static Map merge(Map a, Map b) {
|
||||
var m = new Map.from(a);
|
||||
if (b != null) {
|
||||
b.forEach((k, v) => m[k] = v);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
static List<String> keys(Map<String, dynamic> a) {
|
||||
return a.keys.toList();
|
||||
}
|
||||
|
||||
static bool isEmpty(Map m) => m.isEmpty;
|
||||
static bool equals(Map m1, Map m2) {
|
||||
if (m1.length != m2.length) {
|
||||
return false;
|
||||
}
|
||||
for (var key in m1.keys) {
|
||||
if (m1[key] != m2[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
typedef bool Predicate<T>(T item);
|
||||
|
||||
class ListWrapper {
|
||||
static List clone(Iterable l) => new List.from(l);
|
||||
static List createFixedSize(int size) => new List(size);
|
||||
static List createGrowableSize(int size) =>
|
||||
new List.generate(size, (_) => null, growable: true);
|
||||
static get(List m, int k) => m[k];
|
||||
static void set(List m, int k, v) {
|
||||
m[k] = v;
|
||||
}
|
||||
|
||||
static bool contains(List m, k) => m.contains(k);
|
||||
static List map(list, fn(item)) => list.map(fn).toList();
|
||||
static List filter(List list, bool fn(item)) => list.where(fn).toList();
|
||||
static int indexOf(List list, value, [int startIndex = 0]) =>
|
||||
list.indexOf(value, startIndex);
|
||||
static int lastIndexOf(List list, value, [int startIndex = null]) =>
|
||||
list.lastIndexOf(value, startIndex == null ? list.length : startIndex);
|
||||
static find(List list, bool fn(item)) =>
|
||||
list.firstWhere(fn, orElse: () => null);
|
||||
static bool any(List list, bool fn(item)) => list.any(fn);
|
||||
static void forEach(Iterable list, fn(item)) {
|
||||
list.forEach(fn);
|
||||
}
|
||||
|
||||
static void forEachWithIndex(List list, fn(item, index)) {
|
||||
for (var i = 0; i < list.length; ++i) {
|
||||
fn(list[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
static reduce(List list, fn(a, b), init) {
|
||||
return list.fold(init, fn);
|
||||
}
|
||||
|
||||
static first(List list) => list.isEmpty ? null : list.first;
|
||||
static last(List list) => list.isEmpty ? null : list.last;
|
||||
static List reversed(List list) => list.reversed.toList();
|
||||
static List concat(List a, List b) {
|
||||
return new List()
|
||||
..length = a.length + b.length
|
||||
..setRange(0, a.length, a)
|
||||
..setRange(a.length, a.length + b.length, b);
|
||||
}
|
||||
|
||||
static void insert(List l, int index, value) {
|
||||
l.insert(index, value);
|
||||
}
|
||||
|
||||
static removeAt(List l, int index) => l.removeAt(index);
|
||||
static void removeAll(List list, List items) {
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
list.remove(items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static removeLast(List list) => list.removeLast();
|
||||
static bool remove(List list, item) => list.remove(item);
|
||||
static void clear(List l) {
|
||||
l.clear();
|
||||
}
|
||||
|
||||
static String join(List l, String s) => l.join(s);
|
||||
static bool isEmpty(Iterable list) => list.isEmpty;
|
||||
static void fill(List l, value, [int start = 0, int end]) {
|
||||
l.fillRange(_startOffset(l, start), _endOffset(l, end), value);
|
||||
}
|
||||
|
||||
static bool equals(List a, List b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; ++i) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static List slice(List l, [int from = 0, int to]) {
|
||||
return l.sublist(_startOffset(l, from), _endOffset(l, to));
|
||||
}
|
||||
|
||||
static List splice(List l, int from, int length) {
|
||||
from = _startOffset(l, from);
|
||||
var to = from + length;
|
||||
var sub = l.sublist(from, to);
|
||||
l.removeRange(from, to);
|
||||
return sub;
|
||||
}
|
||||
|
||||
static void sort(List l, [compareFn(a, b) = null]) {
|
||||
if (compareFn == null) {
|
||||
l.sort();
|
||||
} else {
|
||||
l.sort(compareFn);
|
||||
}
|
||||
}
|
||||
|
||||
static String toJSON(List l) {
|
||||
return jsonEncoder.convert(l);
|
||||
}
|
||||
|
||||
// JS splice, slice, fill functions can take start < 0 which indicates a position relative to
|
||||
// the end of the list
|
||||
static int _startOffset(List l, int start) {
|
||||
int len = l.length;
|
||||
return start = start < 0 ? max(len + start, 0) : min(start, len);
|
||||
}
|
||||
|
||||
// JS splice, slice, fill functions can take end < 0 which indicates a position relative to
|
||||
// the end of the list
|
||||
static int _endOffset(List l, int end) {
|
||||
int len = l.length;
|
||||
if (end == null) return len;
|
||||
return end < 0 ? max(len + end, 0) : min(end, len);
|
||||
}
|
||||
|
||||
|
||||
static maximum(List l, fn(item)) {
|
||||
if (l.length == 0) {
|
||||
return null;
|
||||
}
|
||||
var solution = null;
|
||||
var maxValue = double.NEGATIVE_INFINITY;
|
||||
for (var index = 0; index < l.length; index++) {
|
||||
var candidate = l[index];
|
||||
if (candidate == null) {
|
||||
continue;
|
||||
}
|
||||
var candidateValue = fn(candidate);
|
||||
if (candidateValue > maxValue) {
|
||||
solution = candidate;
|
||||
maxValue = candidateValue;
|
||||
}
|
||||
}
|
||||
return solution;
|
||||
}
|
||||
}
|
||||
|
||||
bool isListLikeIterable(obj) => obj is Iterable;
|
||||
|
||||
void iterateListLike(iter, fn(item)) {
|
||||
assert(iter is Iterable);
|
||||
for (var item in iter) {
|
||||
fn(item);
|
||||
}
|
||||
}
|
||||
|
||||
class SetWrapper {
|
||||
static Set createFromList(List l) => new Set.from(l);
|
||||
static bool has(Set s, key) => s.contains(key);
|
||||
static void delete(Set m, k) {
|
||||
m.remove(k);
|
||||
}
|
||||
}
|
339
modules/angular2/src/core/facade/collection.ts
Normal file
339
modules/angular2/src/core/facade/collection.ts
Normal file
@ -0,0 +1,339 @@
|
||||
import {isJsObject, global, isPresent, isBlank, isArray} from 'angular2/src/facade/lang';
|
||||
|
||||
export var List = global.Array;
|
||||
export var Map = global.Map;
|
||||
export var Set = global.Set;
|
||||
export var StringMap = global.Object;
|
||||
|
||||
// Safari and Internet Explorer do not support the iterable parameter to the
|
||||
// Map constructor. We work around that by manually adding the items.
|
||||
var createMapFromPairs: {(pairs: List<any>): Map<any, any>} = (function() {
|
||||
try {
|
||||
if (new Map([[1, 2]]).size === 1) {
|
||||
return function createMapFromPairs(pairs: List<any>):
|
||||
Map<any, any> { return new Map(pairs); };
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
return function createMapAndPopulateFromPairs(pairs: List<any>): Map<any, any> {
|
||||
var map = new Map();
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
var pair = pairs[i];
|
||||
map.set(pair[0], pair[1]);
|
||||
}
|
||||
return map;
|
||||
};
|
||||
})();
|
||||
var createMapFromMap: {(m: Map<any, any>): Map<any, any>} = (function() {
|
||||
try {
|
||||
if (new Map(new Map())) {
|
||||
return function createMapFromMap(m: Map<any, any>): Map<any, any> { return new Map(m); };
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
return function createMapAndPopulateFromMap(m: Map<any, any>): Map<any, any> {
|
||||
var map = new Map();
|
||||
m.forEach((v, k) => { map.set(k, v); });
|
||||
return map;
|
||||
};
|
||||
})();
|
||||
var _clearValues: {(m: Map<any, any>)} = (function() {
|
||||
if ((<any>(new Map()).keys()).next) {
|
||||
return function _clearValues(m: Map<any, any>) {
|
||||
var keyIterator = m.keys();
|
||||
var k;
|
||||
while (!((k = (<any>keyIterator).next()).done)) {
|
||||
m.set(k.value, null);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return function _clearValuesWithForeEach(m: Map<any, any>) {
|
||||
m.forEach((v, k) => { m.set(k, null); });
|
||||
};
|
||||
}
|
||||
})();
|
||||
// Safari doesn't implement MapIterator.next(), which is used is Traceur's polyfill of Array.from
|
||||
// TODO(mlaval): remove the work around once we have a working polyfill of Array.from
|
||||
var _arrayFromMap: {(m: Map<any, any>, getValues: boolean): List<any>} = (function() {
|
||||
try {
|
||||
if ((<any>(new Map()).values()).next) {
|
||||
return function createArrayFromMap(m: Map<any, any>, getValues: boolean): List<any> {
|
||||
return getValues ? (<any>Array).from(m.values()) : (<any>Array).from(m.keys());
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
return function createArrayFromMapWithForeach(m: Map<any, any>, getValues: boolean): List<any> {
|
||||
var res = ListWrapper.createFixedSize(m.size), i = 0;
|
||||
m.forEach((v, k) => {
|
||||
ListWrapper.set(res, i, getValues ? v : k);
|
||||
i++;
|
||||
});
|
||||
return res;
|
||||
};
|
||||
})();
|
||||
|
||||
export class MapWrapper {
|
||||
static clone<K, V>(m: Map<K, V>): Map<K, V> { return createMapFromMap(m); }
|
||||
static createFromStringMap<T>(stringMap: StringMap<string, T>): Map<string, T> {
|
||||
var result = new Map();
|
||||
for (var prop in stringMap) {
|
||||
result.set(prop, stringMap[prop]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static toStringMap<T>(m: Map<string, T>): StringMap<string, T> {
|
||||
var r = {};
|
||||
m.forEach((v, k) => r[k] = v);
|
||||
return r;
|
||||
}
|
||||
static createFromPairs(pairs: List<any>): Map<any, any> { return createMapFromPairs(pairs); }
|
||||
static forEach<K, V>(m: Map<K, V>, fn: /*(V, K) => void*/ Function) { m.forEach(<any>fn); }
|
||||
static get<K, V>(map: Map<K, V>, key: K): V { return map.get(key); }
|
||||
static size(m: Map<any, any>): number { return m.size; }
|
||||
static delete<K>(m: Map<K, any>, k: K) { m.delete(k); }
|
||||
static clearValues(m: Map<any, any>) { _clearValues(m); }
|
||||
static iterable<T>(m: T): T { return m; }
|
||||
static keys<K>(m: Map<K, any>): List<K> { return _arrayFromMap(m, false); }
|
||||
static values<V>(m: Map<any, V>): List<V> { return _arrayFromMap(m, true); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps Javascript Objects
|
||||
*/
|
||||
export class StringMapWrapper {
|
||||
static create(): StringMap<any, any> {
|
||||
// Note: We are not using Object.create(null) here due to
|
||||
// performance!
|
||||
// http://jsperf.com/ng2-object-create-null
|
||||
return {};
|
||||
}
|
||||
static contains(map: StringMap<string, any>, key: string): boolean {
|
||||
return map.hasOwnProperty(key);
|
||||
}
|
||||
static get<V>(map: StringMap<string, V>, key: string): V {
|
||||
return map.hasOwnProperty(key) ? map[key] : undefined;
|
||||
}
|
||||
static set<V>(map: StringMap<string, V>, key: string, value: V) { map[key] = value; }
|
||||
static keys(map: StringMap<string, any>): List<string> { return Object.keys(map); }
|
||||
static isEmpty(map: StringMap<string, any>): boolean {
|
||||
for (var prop in map) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static delete (map: StringMap<string, any>, key: string) { delete map[key]; }
|
||||
static forEach<K, V>(map: StringMap<string, V>, callback: /*(V, K) => void*/ Function) {
|
||||
for (var prop in map) {
|
||||
if (map.hasOwnProperty(prop)) {
|
||||
callback(map[prop], prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static merge<V>(m1: StringMap<string, V>, m2: StringMap<string, V>): StringMap<string, V> {
|
||||
var m = {};
|
||||
|
||||
for (var attr in m1) {
|
||||
if (m1.hasOwnProperty(attr)) {
|
||||
m[attr] = m1[attr];
|
||||
}
|
||||
}
|
||||
|
||||
for (var attr in m2) {
|
||||
if (m2.hasOwnProperty(attr)) {
|
||||
m[attr] = m2[attr];
|
||||
}
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
static equals<V>(m1: StringMap<string, V>, m2: StringMap<string, V>): boolean {
|
||||
var k1 = Object.keys(m1);
|
||||
var k2 = Object.keys(m2);
|
||||
if (k1.length != k2.length) {
|
||||
return false;
|
||||
}
|
||||
var key;
|
||||
for (var i = 0; i < k1.length; i++) {
|
||||
key = k1[i];
|
||||
if (m1[key] !== m2[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export interface Predicate<T> { (value: T, index?: number, array?: T[]): boolean; }
|
||||
|
||||
export class ListWrapper {
|
||||
// JS has no way to express a staticly fixed size list, but dart does so we
|
||||
// keep both methods.
|
||||
static createFixedSize(size: number): List<any> { return new List(size); }
|
||||
static createGrowableSize(size: number): List<any> { return new List(size); }
|
||||
static get<T>(m: List<T>, k: number): T { return m[k]; }
|
||||
static set<T>(m: List<T>, k: number, v: T) { m[k] = v; }
|
||||
static clone<T>(array: List<T>): T[] { return array.slice(0); }
|
||||
static map<T, V>(array: List<T>, fn: (T) => V): List<V> { return array.map(fn); }
|
||||
static forEach<T>(array: List<T>, fn: (T) => void) {
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
fn(array[i]);
|
||||
}
|
||||
}
|
||||
static forEachWithIndex<T>(array: List<T>, fn: (T, number) => void) {
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
fn(array[i], i);
|
||||
}
|
||||
}
|
||||
static first<T>(array: List<T>): T {
|
||||
if (!array) return null;
|
||||
return array[0];
|
||||
}
|
||||
static last<T>(array: List<T>): T {
|
||||
if (!array || array.length == 0) return null;
|
||||
return array[array.length - 1];
|
||||
}
|
||||
static find<T>(list: List<T>, pred: Predicate<T>): T {
|
||||
for (var i = 0; i < list.length; ++i) {
|
||||
if (pred(list[i])) return list[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
static indexOf<T>(array: List<T>, value: T, startIndex: number = 0): number {
|
||||
return array.indexOf(value, startIndex);
|
||||
}
|
||||
static reduce<T, E>(list: List<T>,
|
||||
fn: (accumValue: E, currentValue: T, currentIndex: number, array: T[]) => E,
|
||||
init: E): E {
|
||||
return list.reduce(fn, init);
|
||||
}
|
||||
static filter<T>(array: List<T>, pred: Predicate<T>): T[] { return array.filter(pred); }
|
||||
static any(list: List<any>, pred: Function): boolean {
|
||||
for (var i = 0; i < list.length; ++i) {
|
||||
if (pred(list[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static contains<T>(list: List<T>, el: T): boolean { return list.indexOf(el) !== -1; }
|
||||
static reversed<T>(array: List<T>): T[] {
|
||||
var a = ListWrapper.clone(array);
|
||||
return a.reverse();
|
||||
}
|
||||
static concat(a: List<any>, b: List<any>): List<any> { return a.concat(b); }
|
||||
static insert<T>(list: List<T>, index: number, value: T) { list.splice(index, 0, value); }
|
||||
static removeAt<T>(list: List<T>, index: number): T {
|
||||
var res = list[index];
|
||||
list.splice(index, 1);
|
||||
return res;
|
||||
}
|
||||
static removeAll<T>(list: List<T>, items: List<T>) {
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
var index = list.indexOf(items[i]);
|
||||
list.splice(index, 1);
|
||||
}
|
||||
}
|
||||
static removeLast<T>(list: List<T>): T { return list.pop(); }
|
||||
static remove<T>(list: List<T>, el: T): boolean {
|
||||
var index = list.indexOf(el);
|
||||
if (index > -1) {
|
||||
list.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static clear(list: List<any>) { list.splice(0, list.length); }
|
||||
static join(list: List<any>, s: string): string { return list.join(s); }
|
||||
static isEmpty(list: List<any>): boolean { return list.length == 0; }
|
||||
static fill(list: List<any>, value: any, start: number = 0, end: number = null) {
|
||||
list.fill(value, start, end === null ? list.length : end);
|
||||
}
|
||||
static equals(a: List<any>, b: List<any>): boolean {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; ++i) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static slice<T>(l: List<T>, from: number = 0, to: number = null): List<T> {
|
||||
return l.slice(from, to === null ? undefined : to);
|
||||
}
|
||||
static splice<T>(l: List<T>, from: number, length: number): List<T> {
|
||||
return l.splice(from, length);
|
||||
}
|
||||
static sort<T>(l: List<T>, compareFn?: (a: T, b: T) => number) {
|
||||
if (isPresent(compareFn)) {
|
||||
l.sort(compareFn);
|
||||
} else {
|
||||
l.sort();
|
||||
}
|
||||
}
|
||||
static toString<T>(l: List<T>): string { return l.toString(); }
|
||||
static toJSON<T>(l: List<T>): string { return JSON.stringify(l); }
|
||||
|
||||
static maximum<T>(list: List<T>, predicate: (T) => number): T {
|
||||
if (list.length == 0) {
|
||||
return null;
|
||||
}
|
||||
var solution = null;
|
||||
var maxValue = -Infinity;
|
||||
for (var index = 0; index < list.length; index++) {
|
||||
var candidate = list[index];
|
||||
if (isBlank(candidate)) {
|
||||
continue;
|
||||
}
|
||||
var candidateValue = predicate(candidate);
|
||||
if (candidateValue > maxValue) {
|
||||
solution = candidate;
|
||||
maxValue = candidateValue;
|
||||
}
|
||||
}
|
||||
return solution;
|
||||
}
|
||||
}
|
||||
|
||||
export function isListLikeIterable(obj: any): boolean {
|
||||
if (!isJsObject(obj)) return false;
|
||||
return isArray(obj) ||
|
||||
(!(obj instanceof Map) && // JS Map are iterables but return entries as [k, v]
|
||||
Symbol.iterator in obj); // JS Iterable have a Symbol.iterator prop
|
||||
}
|
||||
|
||||
export function iterateListLike(obj: any, fn: Function) {
|
||||
if (isArray(obj)) {
|
||||
for (var i = 0; i < obj.length; i++) {
|
||||
fn(obj[i]);
|
||||
}
|
||||
} else {
|
||||
var iterator = obj[Symbol.iterator]();
|
||||
var item;
|
||||
while (!((item = iterator.next()).done)) {
|
||||
fn(item.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Safari and Internet Explorer do not support the iterable parameter to the
|
||||
// Set constructor. We work around that by manually adding the items.
|
||||
var createSetFromList: {(lst: List<any>): Set<any>} = (function() {
|
||||
var test = new Set([1, 2, 3]);
|
||||
if (test.size === 3) {
|
||||
return function createSetFromList(lst: List<any>): Set<any> { return new Set(lst); };
|
||||
} else {
|
||||
return function createSetAndPopulateFromList(lst: List<any>): Set<any> {
|
||||
var res = new Set(lst);
|
||||
if (res.size !== lst.length) {
|
||||
for (var i = 0; i < lst.length; i++) {
|
||||
res.add(lst[i]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
}
|
||||
})();
|
||||
export class SetWrapper {
|
||||
static createFromList<T>(lst: List<T>): Set<T> { return createSetFromList(lst); }
|
||||
static has<T>(s: Set<T>, key: T): boolean { return s.has(key); }
|
||||
static delete<K>(m: Set<K>, k: K) { m.delete(k); }
|
||||
}
|
57
modules/angular2/src/core/facade/intl.dart
Normal file
57
modules/angular2/src/core/facade/intl.dart
Normal file
@ -0,0 +1,57 @@
|
||||
library facade.intl;
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
String _normalizeLocale(String locale) => locale.replaceAll('-', '_');
|
||||
|
||||
enum NumberFormatStyle { DECIMAL, PERCENT, CURRENCY }
|
||||
|
||||
class NumberFormatter {
|
||||
static String format(num number, String locale, NumberFormatStyle style,
|
||||
{int minimumIntegerDigits: 1,
|
||||
int minimumFractionDigits: 0,
|
||||
int maximumFractionDigits: 3,
|
||||
String currency,
|
||||
bool currencyAsSymbol: false}) {
|
||||
locale = _normalizeLocale(locale);
|
||||
NumberFormat formatter;
|
||||
switch (style) {
|
||||
case NumberFormatStyle.DECIMAL:
|
||||
formatter = new NumberFormat.decimalPattern(locale);
|
||||
break;
|
||||
case NumberFormatStyle.PERCENT:
|
||||
formatter = new NumberFormat.percentPattern(locale);
|
||||
break;
|
||||
case NumberFormatStyle.CURRENCY:
|
||||
if (currencyAsSymbol) {
|
||||
// See https://github.com/dart-lang/intl/issues/59.
|
||||
throw new Exception(
|
||||
'Displaying currency as symbol is not supported.');
|
||||
}
|
||||
formatter = new NumberFormat.currencyPattern(locale, currency);
|
||||
break;
|
||||
}
|
||||
formatter.minimumIntegerDigits = minimumIntegerDigits;
|
||||
formatter.minimumFractionDigits = minimumFractionDigits;
|
||||
formatter.maximumFractionDigits = maximumFractionDigits;
|
||||
return formatter.format(number);
|
||||
}
|
||||
}
|
||||
|
||||
class DateFormatter {
|
||||
static RegExp _multiPartRegExp = new RegExp(r'^([yMdE]+)([Hjms]+)$');
|
||||
|
||||
static String format(DateTime date, String locale, String pattern) {
|
||||
locale = _normalizeLocale(locale);
|
||||
var formatter = new DateFormat(null, locale);
|
||||
var matches = _multiPartRegExp.firstMatch(pattern);
|
||||
if (matches != null) {
|
||||
// Support for patterns which have known date and time components.
|
||||
formatter.addPattern(matches[1]);
|
||||
formatter.addPattern(matches[2], ', ');
|
||||
} else {
|
||||
formatter.addPattern(pattern);
|
||||
}
|
||||
return formatter.format(date);
|
||||
}
|
||||
}
|
147
modules/angular2/src/core/facade/intl.ts
Normal file
147
modules/angular2/src/core/facade/intl.ts
Normal file
@ -0,0 +1,147 @@
|
||||
|
||||
// Modified version of internal Typescript intl.d.ts.
|
||||
// TODO(piloopin): remove when https://github.com/Microsoft/TypeScript/issues/3521 is shipped.
|
||||
declare module Intl {
|
||||
interface NumberFormatOptions {
|
||||
localeMatcher?: string;
|
||||
style?: string;
|
||||
currency?: string;
|
||||
currencyDisplay?: string;
|
||||
useGrouping?: boolean;
|
||||
minimumIntegerDigits?: number;
|
||||
minimumFractionDigits?: number;
|
||||
maximumFractionDigits?: number;
|
||||
}
|
||||
|
||||
interface NumberFormat {
|
||||
format(value: number): string;
|
||||
}
|
||||
|
||||
var NumberFormat: {new (locale?: string, options?: NumberFormatOptions): NumberFormat};
|
||||
|
||||
interface DateTimeFormatOptions {
|
||||
localeMatcher?: string;
|
||||
weekday?: string;
|
||||
era?: string;
|
||||
year?: string;
|
||||
month?: string;
|
||||
day?: string;
|
||||
hour?: string;
|
||||
minute?: string;
|
||||
second?: string;
|
||||
timeZoneName?: string;
|
||||
formatMatcher?: string;
|
||||
hour12?: boolean;
|
||||
}
|
||||
|
||||
interface DateTimeFormat {
|
||||
format(date?: Date | number): string;
|
||||
}
|
||||
|
||||
var DateTimeFormat: {new (locale?: string, options?: DateTimeFormatOptions): DateTimeFormat};
|
||||
}
|
||||
|
||||
export enum NumberFormatStyle {
|
||||
DECIMAL,
|
||||
PERCENT,
|
||||
CURRENCY
|
||||
}
|
||||
|
||||
export class NumberFormatter {
|
||||
static format(number: number, locale: string, style: NumberFormatStyle,
|
||||
{minimumIntegerDigits = 1, minimumFractionDigits = 0, maximumFractionDigits = 3,
|
||||
currency, currencyAsSymbol = false}: {
|
||||
minimumIntegerDigits?: number,
|
||||
minimumFractionDigits?: number,
|
||||
maximumFractionDigits?: number,
|
||||
currency?: string,
|
||||
currencyAsSymbol?: boolean
|
||||
} = {}): string {
|
||||
var intlOptions: Intl.NumberFormatOptions = {
|
||||
minimumIntegerDigits: minimumIntegerDigits,
|
||||
minimumFractionDigits: minimumFractionDigits,
|
||||
maximumFractionDigits: maximumFractionDigits
|
||||
};
|
||||
intlOptions.style = NumberFormatStyle[style].toLowerCase();
|
||||
if (style == NumberFormatStyle.CURRENCY) {
|
||||
intlOptions.currency = currency;
|
||||
intlOptions.currencyDisplay = currencyAsSymbol ? 'symbol' : 'code';
|
||||
}
|
||||
return new Intl.NumberFormat(locale, intlOptions).format(number);
|
||||
}
|
||||
}
|
||||
|
||||
function digitCondition(len: number): string {
|
||||
return len == 2 ? '2-digit' : 'numeric';
|
||||
}
|
||||
function nameCondition(len: number): string {
|
||||
return len < 4 ? 'short' : 'long';
|
||||
}
|
||||
function extractComponents(pattern: string): Intl.DateTimeFormatOptions {
|
||||
var ret: Intl.DateTimeFormatOptions = {};
|
||||
var i = 0, j;
|
||||
while (i < pattern.length) {
|
||||
j = i;
|
||||
while (j < pattern.length && pattern[j] == pattern[i]) j++;
|
||||
let len = j - i;
|
||||
switch (pattern[i]) {
|
||||
case 'G':
|
||||
ret.era = nameCondition(len);
|
||||
break;
|
||||
case 'y':
|
||||
ret.year = digitCondition(len);
|
||||
break;
|
||||
case 'M':
|
||||
if (len >= 3)
|
||||
ret.month = nameCondition(len);
|
||||
else
|
||||
ret.month = digitCondition(len);
|
||||
break;
|
||||
case 'd':
|
||||
ret.day = digitCondition(len);
|
||||
break;
|
||||
case 'E':
|
||||
ret.weekday = nameCondition(len);
|
||||
break;
|
||||
case 'j':
|
||||
ret.hour = digitCondition(len);
|
||||
break;
|
||||
case 'h':
|
||||
ret.hour = digitCondition(len);
|
||||
ret.hour12 = true;
|
||||
break;
|
||||
case 'H':
|
||||
ret.hour = digitCondition(len);
|
||||
ret.hour12 = false;
|
||||
break;
|
||||
case 'm':
|
||||
ret.minute = digitCondition(len);
|
||||
break;
|
||||
case 's':
|
||||
ret.second = digitCondition(len);
|
||||
break;
|
||||
case 'z':
|
||||
ret.timeZoneName = 'long';
|
||||
break;
|
||||
case 'Z':
|
||||
ret.timeZoneName = 'short';
|
||||
break;
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
var dateFormatterCache: Map<string, Intl.DateTimeFormat> = new Map<string, Intl.DateTimeFormat>();
|
||||
|
||||
export class DateFormatter {
|
||||
static format(date: Date, locale: string, pattern: string): string {
|
||||
var key = locale + pattern;
|
||||
if (dateFormatterCache.has(key)) {
|
||||
return dateFormatterCache.get(key).format(date);
|
||||
}
|
||||
var formatter = new Intl.DateTimeFormat(locale, extractComponents(pattern));
|
||||
dateFormatterCache.set(key, formatter);
|
||||
return formatter.format(date);
|
||||
}
|
||||
}
|
297
modules/angular2/src/core/facade/lang.dart
Normal file
297
modules/angular2/src/core/facade/lang.dart
Normal file
@ -0,0 +1,297 @@
|
||||
library angular.core.facade.lang;
|
||||
|
||||
export 'dart:core' show Type, RegExp, print, DateTime;
|
||||
import 'dart:math' as math;
|
||||
import 'dart:convert' as convert;
|
||||
import 'dart:async' show Future;
|
||||
|
||||
String getTypeNameForDebugging(Type type) => type.toString();
|
||||
|
||||
class Math {
|
||||
static final _random = new math.Random();
|
||||
static int floor(num n) => n.floor();
|
||||
static double random() => _random.nextDouble();
|
||||
}
|
||||
|
||||
int ENUM_INDEX(value) => value.index;
|
||||
|
||||
class CONST {
|
||||
const CONST();
|
||||
}
|
||||
|
||||
class ABSTRACT {
|
||||
const ABSTRACT();
|
||||
}
|
||||
|
||||
class IMPLEMENTS {
|
||||
final interfaceClass;
|
||||
const IMPLEMENTS(this.interfaceClass);
|
||||
}
|
||||
|
||||
bool isPresent(obj) => obj != null;
|
||||
bool isBlank(obj) => obj == null;
|
||||
bool isString(obj) => obj is String;
|
||||
bool isFunction(obj) => obj is Function;
|
||||
bool isType(obj) => obj is Type;
|
||||
bool isStringMap(obj) => obj is Map;
|
||||
bool isArray(obj) => obj is List;
|
||||
bool isPromise(obj) => obj is Future;
|
||||
bool isNumber(obj) => obj is num;
|
||||
bool isDate(obj) => obj is DateTime;
|
||||
|
||||
String stringify(obj) => obj.toString();
|
||||
|
||||
int serializeEnum(val) {
|
||||
return val.index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes an enum
|
||||
* val should be the indexed value of the enum (sa returned from @Link{serializeEnum})
|
||||
* values should be a map from indexes to values for the enum that you want to deserialize.
|
||||
*/
|
||||
dynamic deserializeEnum(num val, Map<num, dynamic> values) {
|
||||
return values[val];
|
||||
}
|
||||
|
||||
class StringWrapper {
|
||||
static String fromCharCode(int code) {
|
||||
return new String.fromCharCode(code);
|
||||
}
|
||||
|
||||
static int charCodeAt(String s, int index) {
|
||||
return s.codeUnitAt(index);
|
||||
}
|
||||
|
||||
static List<String> split(String s, RegExp regExp) {
|
||||
var parts = <String>[];
|
||||
var lastEnd = 0;
|
||||
regExp.allMatches(s).forEach((match) {
|
||||
parts.add(s.substring(lastEnd, match.start));
|
||||
lastEnd = match.end;
|
||||
for (var i = 0; i < match.groupCount; i++) {
|
||||
parts.add(match.group(i + 1));
|
||||
}
|
||||
});
|
||||
parts.add(s.substring(lastEnd));
|
||||
return parts;
|
||||
}
|
||||
|
||||
static bool equals(String s, String s2) {
|
||||
return s == s2;
|
||||
}
|
||||
|
||||
static String replace(String s, Pattern from, String replace) {
|
||||
return s.replaceFirst(from, replace);
|
||||
}
|
||||
|
||||
static String replaceAll(String s, RegExp from, String replace) {
|
||||
return s.replaceAll(from, replace);
|
||||
}
|
||||
|
||||
static String toUpperCase(String s) {
|
||||
return s.toUpperCase();
|
||||
}
|
||||
|
||||
static String toLowerCase(String s) {
|
||||
return s.toLowerCase();
|
||||
}
|
||||
|
||||
static startsWith(String s, String start) {
|
||||
return s.startsWith(start);
|
||||
}
|
||||
|
||||
static String substring(String s, int start, [int end]) {
|
||||
return s.substring(start, end);
|
||||
}
|
||||
|
||||
static String replaceAllMapped(String s, RegExp from, Function cb) {
|
||||
return s.replaceAllMapped(from, cb);
|
||||
}
|
||||
|
||||
static bool contains(String s, String substr) {
|
||||
return s.contains(substr);
|
||||
}
|
||||
|
||||
static int compare(String a, String b) => a.compareTo(b);
|
||||
}
|
||||
|
||||
class StringJoiner {
|
||||
final List<String> _parts = <String>[];
|
||||
|
||||
void add(String part) {
|
||||
_parts.add(part);
|
||||
}
|
||||
|
||||
String toString() => _parts.join("");
|
||||
}
|
||||
|
||||
class NumberWrapper {
|
||||
static String toFixed(num n, int fractionDigits) {
|
||||
return n.toStringAsFixed(fractionDigits);
|
||||
}
|
||||
|
||||
static bool equal(num a, num b) {
|
||||
return a == b;
|
||||
}
|
||||
|
||||
static int parseIntAutoRadix(String text) {
|
||||
return int.parse(text);
|
||||
}
|
||||
|
||||
static int parseInt(String text, int radix) {
|
||||
return int.parse(text, radix: radix);
|
||||
}
|
||||
|
||||
static double parseFloat(String text) {
|
||||
return double.parse(text);
|
||||
}
|
||||
|
||||
static double get NaN => double.NAN;
|
||||
|
||||
static bool isNaN(num value) => value.isNaN;
|
||||
|
||||
static bool isInteger(value) => value is int;
|
||||
}
|
||||
|
||||
class RegExpWrapper {
|
||||
static RegExp create(regExpStr, [String flags = '']) {
|
||||
bool multiLine = flags.contains('m');
|
||||
bool caseSensitive = !flags.contains('i');
|
||||
return new RegExp(regExpStr,
|
||||
multiLine: multiLine, caseSensitive: caseSensitive);
|
||||
}
|
||||
|
||||
static Match firstMatch(RegExp regExp, String input) {
|
||||
return regExp.firstMatch(input);
|
||||
}
|
||||
|
||||
static bool test(RegExp regExp, String input) {
|
||||
return regExp.hasMatch(input);
|
||||
}
|
||||
|
||||
static Iterator<Match> matcher(RegExp regExp, String input) {
|
||||
return regExp.allMatches(input).iterator;
|
||||
}
|
||||
}
|
||||
|
||||
class RegExpMatcherWrapper {
|
||||
static _JSLikeMatch next(Iterator<Match> matcher) {
|
||||
if (matcher.moveNext()) {
|
||||
return new _JSLikeMatch(matcher.current);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class _JSLikeMatch {
|
||||
Match _m;
|
||||
|
||||
_JSLikeMatch(this._m);
|
||||
|
||||
String operator [](index) => _m[index];
|
||||
int get index => _m.start;
|
||||
int get length => _m.groupCount + 1;
|
||||
}
|
||||
|
||||
class FunctionWrapper {
|
||||
static apply(Function fn, posArgs) {
|
||||
return Function.apply(fn, posArgs);
|
||||
}
|
||||
}
|
||||
|
||||
class BaseException extends Error {
|
||||
final dynamic context;
|
||||
final String message;
|
||||
final originalException;
|
||||
final originalStack;
|
||||
|
||||
BaseException(
|
||||
[this.message, this.originalException, this.originalStack, this.context]);
|
||||
|
||||
String toString() {
|
||||
return this.message;
|
||||
}
|
||||
}
|
||||
|
||||
Error makeTypeError([String message = ""]) {
|
||||
return new BaseException(message);
|
||||
}
|
||||
|
||||
const _NAN_KEY = const Object();
|
||||
|
||||
// Dart can have identical(str1, str2) == false while str1 == str2. Moreover,
|
||||
// after compiling with dart2js identical(str1, str2) might return true.
|
||||
// (see dartbug.com/22496 for details).
|
||||
bool looseIdentical(a, b) =>
|
||||
a is String && b is String ? a == b : identical(a, b);
|
||||
|
||||
// Dart compare map keys by equality and we can have NaN != NaN
|
||||
dynamic getMapKey(value) {
|
||||
if (value is! num) return value;
|
||||
return value.isNaN ? _NAN_KEY : value;
|
||||
}
|
||||
|
||||
// TODO: remove with https://github.com/angular/angular/issues/3055
|
||||
dynamic normalizeBlank(obj) => obj;
|
||||
|
||||
bool normalizeBool(bool obj) {
|
||||
return isBlank(obj) ? false : obj;
|
||||
}
|
||||
|
||||
bool isJsObject(o) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var _assertionsEnabled = null;
|
||||
bool assertionsEnabled() {
|
||||
if (_assertionsEnabled == null) {
|
||||
try {
|
||||
assert(false);
|
||||
_assertionsEnabled = false;
|
||||
} catch (e) {
|
||||
_assertionsEnabled = true;
|
||||
}
|
||||
}
|
||||
return _assertionsEnabled;
|
||||
}
|
||||
|
||||
// Can't be all uppercase as our transpiler would think it is a special directive...
|
||||
class Json {
|
||||
static parse(String s) => convert.JSON.decode(s);
|
||||
static String stringify(data) {
|
||||
var encoder = new convert.JsonEncoder.withIndent(" ");
|
||||
return encoder.convert(data);
|
||||
}
|
||||
}
|
||||
|
||||
class DateWrapper {
|
||||
static DateTime create(int year,
|
||||
[int month = 1,
|
||||
int day = 1,
|
||||
int hour = 0,
|
||||
int minutes = 0,
|
||||
int seconds = 0,
|
||||
int milliseconds = 0]) {
|
||||
return new DateTime(year, month, day, hour, minutes, seconds, milliseconds);
|
||||
}
|
||||
|
||||
static DateTime fromMillis(int ms) {
|
||||
return new DateTime.fromMillisecondsSinceEpoch(ms, isUtc: true);
|
||||
}
|
||||
|
||||
static int toMillis(DateTime date) {
|
||||
return date.millisecondsSinceEpoch;
|
||||
}
|
||||
|
||||
static DateTime now() {
|
||||
return new DateTime.now();
|
||||
}
|
||||
|
||||
static String toJson(DateTime date) {
|
||||
return date.toUtc().toIso8601String();
|
||||
}
|
||||
}
|
||||
|
||||
// needed to match the exports from lang.js
|
||||
var global = null;
|
349
modules/angular2/src/core/facade/lang.ts
Normal file
349
modules/angular2/src/core/facade/lang.ts
Normal file
@ -0,0 +1,349 @@
|
||||
/// <reference path="../../globals.d.ts" />
|
||||
var _global: BrowserNodeGlobal = <any>(typeof window === 'undefined' ? global : window);
|
||||
export {_global as global};
|
||||
|
||||
export var Type = Function;
|
||||
|
||||
/**
|
||||
* Runtime representation of a type.
|
||||
*
|
||||
* In JavaScript a Type is a constructor function.
|
||||
*/
|
||||
export interface Type extends Function { new (...args): any; }
|
||||
|
||||
export function getTypeNameForDebugging(type: Type): string {
|
||||
return type['name'];
|
||||
}
|
||||
|
||||
export class BaseException extends Error {
|
||||
stack;
|
||||
constructor(public message?: string, private _originalException?, private _originalStack?,
|
||||
private _context?) {
|
||||
super(message);
|
||||
this.stack = (<any>new Error(message)).stack;
|
||||
}
|
||||
|
||||
get originalException(): any { return this._originalException; }
|
||||
|
||||
get originalStack(): any { return this._originalStack; }
|
||||
|
||||
get context(): any { return this._context; }
|
||||
|
||||
toString(): string { return this.message; }
|
||||
}
|
||||
|
||||
export function makeTypeError(message?: string): Error {
|
||||
return new TypeError(message);
|
||||
}
|
||||
|
||||
export var Math = _global.Math;
|
||||
export var Date = _global.Date;
|
||||
|
||||
var assertionsEnabled_ = typeof _global['assert'] !== 'undefined';
|
||||
export function assertionsEnabled(): boolean {
|
||||
return assertionsEnabled_;
|
||||
}
|
||||
|
||||
// TODO: remove calls to assert in production environment
|
||||
// Note: Can't just export this and import in in other files
|
||||
// as `assert` is a reserved keyword in Dart
|
||||
_global.assert = function assert(condition) {
|
||||
if (assertionsEnabled_) {
|
||||
_global['assert'].call(condition);
|
||||
}
|
||||
};
|
||||
|
||||
export function ENUM_INDEX(value: number): number {
|
||||
return value;
|
||||
}
|
||||
|
||||
// This function is needed only to properly support Dart's const expressions
|
||||
// see https://github.com/angular/ts2dart/pull/151 for more info
|
||||
export function CONST_EXPR<T>(expr: T): T {
|
||||
return expr;
|
||||
}
|
||||
|
||||
export function CONST(): ClassDecorator {
|
||||
return (target) => target;
|
||||
}
|
||||
|
||||
export function ABSTRACT(): ClassDecorator {
|
||||
return (t) => t;
|
||||
}
|
||||
|
||||
// Note: This is only a marker annotation needed for ts2dart.
|
||||
// This is written so that is can be used as a Traceur annotation
|
||||
// or a Typescript decorator.
|
||||
export function IMPLEMENTS(_): ClassDecorator {
|
||||
return (t) => t;
|
||||
}
|
||||
|
||||
export function isPresent(obj: any): boolean {
|
||||
return obj !== undefined && obj !== null;
|
||||
}
|
||||
|
||||
export function isBlank(obj: any): boolean {
|
||||
return obj === undefined || obj === null;
|
||||
}
|
||||
|
||||
export function isString(obj: any): boolean {
|
||||
return typeof obj === "string";
|
||||
}
|
||||
|
||||
export function isFunction(obj: any): boolean {
|
||||
return typeof obj === "function";
|
||||
}
|
||||
|
||||
export function isType(obj: any): boolean {
|
||||
return isFunction(obj);
|
||||
}
|
||||
|
||||
export function isStringMap(obj: any): boolean {
|
||||
return typeof obj === 'object' && obj !== null;
|
||||
}
|
||||
|
||||
export function isPromise(obj: any): boolean {
|
||||
return obj instanceof (<any>_global).Promise;
|
||||
}
|
||||
|
||||
export function isArray(obj: any): boolean {
|
||||
return Array.isArray(obj);
|
||||
}
|
||||
|
||||
export function isNumber(obj): boolean {
|
||||
return typeof obj === 'number';
|
||||
}
|
||||
|
||||
export function isDate(obj): boolean {
|
||||
return obj instanceof Date && !isNaN(obj.valueOf());
|
||||
}
|
||||
|
||||
export function stringify(token): string {
|
||||
if (typeof token === 'string') {
|
||||
return token;
|
||||
}
|
||||
|
||||
if (token === undefined || token === null) {
|
||||
return '' + token;
|
||||
}
|
||||
|
||||
if (token.name) {
|
||||
return token.name;
|
||||
}
|
||||
|
||||
var res = token.toString();
|
||||
var newLineIndex = res.indexOf("\n");
|
||||
return (newLineIndex === -1) ? res : res.substring(0, newLineIndex);
|
||||
}
|
||||
|
||||
// serialize / deserialize enum exist only for consistency with dart API
|
||||
// enums in typescript don't need to be serialized
|
||||
|
||||
export function serializeEnum(val): number {
|
||||
return val;
|
||||
}
|
||||
|
||||
export function deserializeEnum(val, values: Map<number, any>): any {
|
||||
return val;
|
||||
}
|
||||
|
||||
export class StringWrapper {
|
||||
static fromCharCode(code: number): string { return String.fromCharCode(code); }
|
||||
|
||||
static charCodeAt(s: string, index: number): number { return s.charCodeAt(index); }
|
||||
|
||||
static split(s: string, regExp: RegExp): List<string> { return s.split(regExp); }
|
||||
|
||||
static equals(s: string, s2: string): boolean { return s === s2; }
|
||||
|
||||
static replace(s: string, from: string, replace: string): string {
|
||||
return s.replace(from, replace);
|
||||
}
|
||||
|
||||
static replaceAll(s: string, from: RegExp, replace: string): string {
|
||||
return s.replace(from, replace);
|
||||
}
|
||||
|
||||
static toUpperCase(s: string): string { return s.toUpperCase(); }
|
||||
|
||||
static toLowerCase(s: string): string { return s.toLowerCase(); }
|
||||
|
||||
static startsWith(s: string, start: string): boolean { return s.startsWith(start); }
|
||||
|
||||
static substring(s: string, start: number, end: number = null): string {
|
||||
return s.substring(start, end === null ? undefined : end);
|
||||
}
|
||||
|
||||
static replaceAllMapped(s: string, from: RegExp, cb: Function): string {
|
||||
return s.replace(from, function(...matches) {
|
||||
// Remove offset & string from the result array
|
||||
matches.splice(-2, 2);
|
||||
// The callback receives match, p1, ..., pn
|
||||
return cb(matches);
|
||||
});
|
||||
}
|
||||
|
||||
static contains(s: string, substr: string): boolean { return s.indexOf(substr) != -1; }
|
||||
|
||||
static compare(a: string, b: string): number {
|
||||
if (a < b) {
|
||||
return -1;
|
||||
} else if (a > b) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class StringJoiner {
|
||||
constructor(public parts = []) {}
|
||||
|
||||
add(part: string): void { this.parts.push(part); }
|
||||
|
||||
toString(): string { return this.parts.join(""); }
|
||||
}
|
||||
|
||||
export class NumberParseError extends BaseException {
|
||||
name: string;
|
||||
|
||||
constructor(public message: string) { super(); }
|
||||
|
||||
toString(): string { return this.message; }
|
||||
}
|
||||
|
||||
|
||||
export class NumberWrapper {
|
||||
static toFixed(n: number, fractionDigits: number): string { return n.toFixed(fractionDigits); }
|
||||
|
||||
static equal(a: number, b: number): boolean { return a === b; }
|
||||
|
||||
static parseIntAutoRadix(text: string): number {
|
||||
var result: number = parseInt(text);
|
||||
if (isNaN(result)) {
|
||||
throw new NumberParseError("Invalid integer literal when parsing " + text);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static parseInt(text: string, radix: number): number {
|
||||
if (radix == 10) {
|
||||
if (/^(\-|\+)?[0-9]+$/.test(text)) {
|
||||
return parseInt(text, radix);
|
||||
}
|
||||
} else if (radix == 16) {
|
||||
if (/^(\-|\+)?[0-9ABCDEFabcdef]+$/.test(text)) {
|
||||
return parseInt(text, radix);
|
||||
}
|
||||
} else {
|
||||
var result: number = parseInt(text, radix);
|
||||
if (!isNaN(result)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
throw new NumberParseError("Invalid integer literal when parsing " + text + " in base " +
|
||||
radix);
|
||||
}
|
||||
|
||||
// TODO: NaN is a valid literal but is returned by parseFloat to indicate an error.
|
||||
static parseFloat(text: string): number { return parseFloat(text); }
|
||||
|
||||
static get NaN(): number { return NaN; }
|
||||
|
||||
static isNaN(value: any): boolean { return isNaN(value); }
|
||||
|
||||
static isInteger(value: any): boolean { return Number.isInteger(value); }
|
||||
}
|
||||
|
||||
export var RegExp = _global.RegExp;
|
||||
|
||||
export class RegExpWrapper {
|
||||
static create(regExpStr: string, flags: string = ''): RegExp {
|
||||
flags = flags.replace(/g/g, '');
|
||||
return new _global.RegExp(regExpStr, flags + 'g');
|
||||
}
|
||||
static firstMatch(regExp: RegExp, input: string): List<string> {
|
||||
// Reset multimatch regex state
|
||||
regExp.lastIndex = 0;
|
||||
return regExp.exec(input);
|
||||
}
|
||||
static test(regExp: RegExp, input: string): boolean {
|
||||
regExp.lastIndex = 0;
|
||||
return regExp.test(input);
|
||||
}
|
||||
static matcher(regExp: RegExp, input: string): {
|
||||
re: RegExp;
|
||||
input: string
|
||||
}
|
||||
{
|
||||
// Reset regex state for the case
|
||||
// someone did not loop over all matches
|
||||
// last time.
|
||||
regExp.lastIndex = 0;
|
||||
return {re: regExp, input: input};
|
||||
}
|
||||
}
|
||||
|
||||
export class RegExpMatcherWrapper {
|
||||
static next(matcher: {
|
||||
re: RegExp;
|
||||
input: string
|
||||
}): string[] {
|
||||
return matcher.re.exec(matcher.input);
|
||||
}
|
||||
}
|
||||
|
||||
export class FunctionWrapper {
|
||||
static apply(fn: Function, posArgs: any): any { return fn.apply(null, posArgs); }
|
||||
}
|
||||
|
||||
// JS has NaN !== NaN
|
||||
export function looseIdentical(a, b): boolean {
|
||||
return a === b || typeof a === "number" && typeof b === "number" && isNaN(a) && isNaN(b);
|
||||
}
|
||||
|
||||
// JS considers NaN is the same as NaN for map Key (while NaN !== NaN otherwise)
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
|
||||
export function getMapKey<T>(value: T): T {
|
||||
return value;
|
||||
}
|
||||
|
||||
export function normalizeBlank(obj: Object): any {
|
||||
return isBlank(obj) ? null : obj;
|
||||
}
|
||||
|
||||
export function normalizeBool(obj: boolean): boolean {
|
||||
return isBlank(obj) ? false : obj;
|
||||
}
|
||||
|
||||
export function isJsObject(o: any): boolean {
|
||||
return o !== null && (typeof o === "function" || typeof o === "object");
|
||||
}
|
||||
|
||||
export function print(obj: Error | Object) {
|
||||
if (obj instanceof BaseException) {
|
||||
console.log(obj.stack);
|
||||
} else {
|
||||
console.log(obj);
|
||||
}
|
||||
}
|
||||
|
||||
// Can't be all uppercase as our transpiler would think it is a special directive...
|
||||
export class Json {
|
||||
static parse(s: string): Object { return _global.JSON.parse(s); }
|
||||
static stringify(data: Object): string {
|
||||
// Dart doesn't take 3 arguments
|
||||
return _global.JSON.stringify(data, null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
export class DateWrapper {
|
||||
static create(year: number, month: number = 1, day: number = 1, hour: number = 0,
|
||||
minutes: number = 0, seconds: number = 0, milliseconds: number = 0): Date {
|
||||
return new Date(year, month - 1, day, hour, minutes, seconds, milliseconds);
|
||||
}
|
||||
static fromMillis(ms: number): Date { return new Date(ms); }
|
||||
static toMillis(date: Date): number { return date.getTime(); }
|
||||
static now(): Date { return new Date(); }
|
||||
static toJson(date: Date): string { return date.toJSON(); }
|
||||
}
|
22
modules/angular2/src/core/facade/math.dart
Normal file
22
modules/angular2/src/core/facade/math.dart
Normal file
@ -0,0 +1,22 @@
|
||||
library angular.core.facade.math;
|
||||
|
||||
import 'dart:core' show double, num;
|
||||
import 'dart:math' as math;
|
||||
|
||||
const NaN = double.NAN;
|
||||
|
||||
class Math {
|
||||
static num pow(num x, num exponent) {
|
||||
return math.pow(x, exponent);
|
||||
}
|
||||
|
||||
static num max(num a, num b) => math.max(a, b);
|
||||
|
||||
static num min(num a, num b) => math.min(a, b);
|
||||
|
||||
static num floor(num a) => a.floor();
|
||||
|
||||
static num ceil(num a) => a.ceil();
|
||||
|
||||
static num sqrt(num x) => math.sqrt(x);
|
||||
}
|
4
modules/angular2/src/core/facade/math.ts
Normal file
4
modules/angular2/src/core/facade/math.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import {global} from 'angular2/src/facade/lang';
|
||||
|
||||
export var Math = global.Math;
|
||||
export var NaN = typeof NaN;
|
130
modules/angular2/src/core/pipes/async_pipe.ts
Normal file
130
modules/angular2/src/core/pipes/async_pipe.ts
Normal file
@ -0,0 +1,130 @@
|
||||
import {isBlank, isPresent, isPromise, CONST, BaseException} from 'angular2/src/facade/lang';
|
||||
import {Observable, Promise, ObservableWrapper} from 'angular2/src/facade/async';
|
||||
import {Injectable} from 'angular2/di';
|
||||
|
||||
import {PipeTransform, PipeOnDestroy, WrappedValue} from 'angular2/change_detection';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
import {ChangeDetectorRef} from 'angular2/change_detection';
|
||||
|
||||
import {Pipe} from '../core/metadata';
|
||||
|
||||
|
||||
class ObservableStrategy {
|
||||
createSubscription(async: any, updateLatestValue: any): any {
|
||||
return ObservableWrapper.subscribe(async, updateLatestValue, e => { throw e; });
|
||||
}
|
||||
|
||||
dispose(subscription: any): void { ObservableWrapper.dispose(subscription); }
|
||||
|
||||
onDestroy(subscription: any): void { ObservableWrapper.dispose(subscription); }
|
||||
}
|
||||
|
||||
class PromiseStrategy {
|
||||
createSubscription(async: any, updateLatestValue: any): any {
|
||||
return async.then(updateLatestValue);
|
||||
}
|
||||
|
||||
dispose(subscription: any): void {}
|
||||
|
||||
onDestroy(subscription: any): void {}
|
||||
}
|
||||
|
||||
var _promiseStrategy = new PromiseStrategy();
|
||||
var _observableStrategy = new ObservableStrategy();
|
||||
|
||||
|
||||
/**
|
||||
* Implements async bindings to Observable and Promise.
|
||||
*
|
||||
* # Example
|
||||
*
|
||||
* In this example we bind the description observable to the DOM. The async pipe will convert an
|
||||
*observable to the
|
||||
* latest value it emitted. It will also request a change detection check when a new value is
|
||||
*emitted.
|
||||
*
|
||||
* ```
|
||||
* @Component({
|
||||
* selector: "task-cmp",
|
||||
* changeDetection: ON_PUSH
|
||||
* })
|
||||
* @View({
|
||||
* template: "Task Description {{ description | async }}"
|
||||
* })
|
||||
* class Task {
|
||||
* description:Observable<string>;
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
@Pipe({name: 'async'})
|
||||
@Injectable()
|
||||
export class AsyncPipe implements PipeTransform, PipeOnDestroy {
|
||||
_latestValue: Object = null;
|
||||
_latestReturnedValue: Object = null;
|
||||
|
||||
_subscription: Object = null;
|
||||
_obj: Observable | Promise<any> = null;
|
||||
private _strategy: any = null;
|
||||
|
||||
constructor(public _ref: ChangeDetectorRef) {}
|
||||
|
||||
onDestroy(): void {
|
||||
if (isPresent(this._subscription)) {
|
||||
this._dispose();
|
||||
}
|
||||
}
|
||||
|
||||
transform(obj: Observable | Promise<any>, args?: any[]): any {
|
||||
if (isBlank(this._obj)) {
|
||||
if (isPresent(obj)) {
|
||||
this._subscribe(obj);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (obj !== this._obj) {
|
||||
this._dispose();
|
||||
return this.transform(obj);
|
||||
}
|
||||
|
||||
if (this._latestValue === this._latestReturnedValue) {
|
||||
return this._latestReturnedValue;
|
||||
} else {
|
||||
this._latestReturnedValue = this._latestValue;
|
||||
return WrappedValue.wrap(this._latestValue);
|
||||
}
|
||||
}
|
||||
|
||||
_subscribe(obj: Observable | Promise<any>): void {
|
||||
this._obj = obj;
|
||||
this._strategy = this._selectStrategy(obj);
|
||||
this._subscription =
|
||||
this._strategy.createSubscription(obj, value => this._updateLatestValue(obj, value));
|
||||
}
|
||||
|
||||
_selectStrategy(obj: Observable | Promise<any>): any {
|
||||
if (isPromise(obj)) {
|
||||
return _promiseStrategy;
|
||||
} else if (ObservableWrapper.isObservable(obj)) {
|
||||
return _observableStrategy;
|
||||
} else {
|
||||
throw new InvalidPipeArgumentException(AsyncPipe, obj);
|
||||
}
|
||||
}
|
||||
|
||||
_dispose(): void {
|
||||
this._strategy.dispose(this._subscription);
|
||||
this._latestValue = null;
|
||||
this._latestReturnedValue = null;
|
||||
this._subscription = null;
|
||||
this._obj = null;
|
||||
}
|
||||
|
||||
_updateLatestValue(async: any, value: Object) {
|
||||
if (async === this._obj) {
|
||||
this._latestValue = value;
|
||||
this._ref.requestCheck();
|
||||
}
|
||||
}
|
||||
}
|
114
modules/angular2/src/core/pipes/date_pipe.ts
Normal file
114
modules/angular2/src/core/pipes/date_pipe.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import {
|
||||
isDate,
|
||||
isNumber,
|
||||
isPresent,
|
||||
Date,
|
||||
DateWrapper,
|
||||
CONST,
|
||||
isBlank,
|
||||
FunctionWrapper
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {DateFormatter} from 'angular2/src/facade/intl';
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {PipeTransform, WrappedValue} from 'angular2/change_detection';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
import {Pipe} from '../core/metadata';
|
||||
|
||||
// TODO: move to a global configable location along with other i18n components.
|
||||
var defaultLocale: string = 'en-US';
|
||||
|
||||
/**
|
||||
* WARNING: this pipe uses the Internationalization API.
|
||||
* Therefore it is only reliable in Chrome and Opera browsers.
|
||||
*
|
||||
* Formats a date value to a string based on the requested format.
|
||||
*
|
||||
* # Usage
|
||||
*
|
||||
* expression | date[:format]
|
||||
*
|
||||
* where `expression` is a date object or a number (milliseconds since UTC epoch) and
|
||||
* `format` indicates which date/time components to include:
|
||||
*
|
||||
* | Component | Symbol | Short Form | Long Form | Numeric | 2-digit |
|
||||
* |-----------|:------:|--------------|-------------------|-----------|-----------|
|
||||
* | era | G | G (AD) | GGGG (Anno Domini)| - | - |
|
||||
* | year | y | - | - | y (2015) | yy (15) |
|
||||
* | month | M | MMM (Sep) | MMMM (September) | M (9) | MM (09) |
|
||||
* | day | d | - | - | d (3) | dd (03) |
|
||||
* | weekday | E | EEE (Sun) | EEEE (Sunday) | - | - |
|
||||
* | hour | j | - | - | j (13) | jj (13) |
|
||||
* | hour12 | h | - | - | h (1 PM) | hh (01 PM)|
|
||||
* | hour24 | H | - | - | H (13) | HH (13) |
|
||||
* | minute | m | - | - | m (5) | mm (05) |
|
||||
* | second | s | - | - | s (9) | ss (09) |
|
||||
* | timezone | z | - | z (Pacific Standard Time)| - | - |
|
||||
* | timezone | Z | Z (GMT-8:00) | - | - | - |
|
||||
*
|
||||
* In javascript, only the components specified will be respected (not the ordering,
|
||||
* punctuations, ...) and details of the the formatting will be dependent on the locale.
|
||||
* On the other hand in Dart version, you can also include quoted text as well as some extra
|
||||
* date/time components such as quarter. For more information see:
|
||||
* https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/intl/intl.DateFormat.
|
||||
*
|
||||
* `format` can also be one of the following predefined formats:
|
||||
*
|
||||
* - `'medium'`: equivalent to `'yMMMdjms'` (e.g. Sep 3, 2010, 12:05:08 PM for en-US)
|
||||
* - `'short'`: equivalent to `'yMdjm'` (e.g. 9/3/2010, 12:05 PM for en-US)
|
||||
* - `'fullDate'`: equivalent to `'yMMMMEEEEd'` (e.g. Friday, September 3, 2010 for en-US)
|
||||
* - `'longDate'`: equivalent to `'yMMMMd'` (e.g. September 3, 2010)
|
||||
* - `'mediumDate'`: equivalent to `'yMMMd'` (e.g. Sep 3, 2010 for en-US)
|
||||
* - `'shortDate'`: equivalent to `'yMd'` (e.g. 9/3/2010 for en-US)
|
||||
* - `'mediumTime'`: equivalent to `'jms'` (e.g. 12:05:08 PM for en-US)
|
||||
* - `'shortTime'`: equivalent to `'jm'` (e.g. 12:05 PM for en-US)
|
||||
*
|
||||
* Timezone of the formatted text will be the local system timezone of the end-users machine.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* Assuming `dateObj` is (year: 2015, month: 6, day: 15, hour: 21, minute: 43, second: 11)
|
||||
* in the _local_ time and locale is 'en-US':
|
||||
*
|
||||
* {{ dateObj | date }} // output is 'Jun 15, 2015'
|
||||
* {{ dateObj | date:'medium' }} // output is 'Jun 15, 2015, 9:43:11 PM'
|
||||
* {{ dateObj | date:'shortTime' }} // output is '9:43 PM'
|
||||
* {{ dateObj | date:'mmss' }} // output is '43:11'
|
||||
*/
|
||||
@CONST()
|
||||
@Pipe({name: 'date'})
|
||||
@Injectable()
|
||||
export class DatePipe implements PipeTransform {
|
||||
static _ALIASES = {
|
||||
'medium': 'yMMMdjms',
|
||||
'short': 'yMdjm',
|
||||
'fullDate': 'yMMMMEEEEd',
|
||||
'longDate': 'yMMMMd',
|
||||
'mediumDate': 'yMMMd',
|
||||
'shortDate': 'yMd',
|
||||
'mediumTime': 'jms',
|
||||
'shortTime': 'jm'
|
||||
};
|
||||
|
||||
|
||||
transform(value: any, args: List<any>): string {
|
||||
if (isBlank(value)) return null;
|
||||
|
||||
if (!this.supports(value)) {
|
||||
throw new InvalidPipeArgumentException(DatePipe, value);
|
||||
}
|
||||
|
||||
var pattern: string = isPresent(args) && args.length > 0 ? args[0] : 'mediumDate';
|
||||
if (isNumber(value)) {
|
||||
value = DateWrapper.fromMillis(value);
|
||||
}
|
||||
if (StringMapWrapper.contains(DatePipe._ALIASES, pattern)) {
|
||||
pattern = <string>StringMapWrapper.get(DatePipe._ALIASES, pattern);
|
||||
}
|
||||
return DateFormatter.format(value, defaultLocale, pattern);
|
||||
}
|
||||
|
||||
private supports(obj: any): boolean { return isDate(obj) || isNumber(obj); }
|
||||
}
|
27
modules/angular2/src/core/pipes/default_pipes.ts
Normal file
27
modules/angular2/src/core/pipes/default_pipes.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import {AsyncPipe} from './async_pipe';
|
||||
import {UpperCasePipe} from './uppercase_pipe';
|
||||
import {LowerCasePipe} from './lowercase_pipe';
|
||||
import {JsonPipe} from './json_pipe';
|
||||
import {LimitToPipe} from './limit_to_pipe';
|
||||
import {DatePipe} from './date_pipe';
|
||||
import {DecimalPipe, PercentPipe, CurrencyPipe} from './number_pipe';
|
||||
|
||||
import {CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
import {Binding, OpaqueToken} from 'angular2/di';
|
||||
|
||||
const DEFAULT_PIPES_LIST = CONST_EXPR([
|
||||
AsyncPipe,
|
||||
UpperCasePipe,
|
||||
LowerCasePipe,
|
||||
JsonPipe,
|
||||
LimitToPipe,
|
||||
DecimalPipe,
|
||||
PercentPipe,
|
||||
CurrencyPipe,
|
||||
DatePipe
|
||||
]);
|
||||
|
||||
export const DEFAULT_PIPES_TOKEN = CONST_EXPR(new OpaqueToken("Default Pipes"));
|
||||
|
||||
export const DEFAULT_PIPES =
|
||||
CONST_EXPR(new Binding(DEFAULT_PIPES_TOKEN, {toValue: DEFAULT_PIPES_LIST}));
|
@ -0,0 +1,7 @@
|
||||
import {ABSTRACT, BaseException, CONST, Type} from 'angular2/src/facade/lang';
|
||||
|
||||
export class InvalidPipeArgumentException extends BaseException {
|
||||
constructor(type: Type, value: Object) {
|
||||
super(`Invalid argument '${value}' for pipe '${type}'`);
|
||||
}
|
||||
}
|
36
modules/angular2/src/core/pipes/json_pipe.ts
Normal file
36
modules/angular2/src/core/pipes/json_pipe.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import {isBlank, isPresent, Json, CONST} from 'angular2/src/facade/lang';
|
||||
import {Injectable} from 'angular2/di';
|
||||
|
||||
import {PipeTransform, WrappedValue} from 'angular2/change_detection';
|
||||
|
||||
import {Pipe} from '../core/metadata';
|
||||
|
||||
/**
|
||||
* Implements json transforms to any object.
|
||||
*
|
||||
* # Example
|
||||
*
|
||||
* In this example we transform the user object to json.
|
||||
*
|
||||
* ```
|
||||
* @Component({
|
||||
* selector: "user-cmp"
|
||||
* })
|
||||
* @View({
|
||||
* template: "User: {{ user | json }}"
|
||||
* })
|
||||
* class Username {
|
||||
* user:Object
|
||||
* constructor() {
|
||||
* this.user = { name: "PatrickJS" };
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
@CONST()
|
||||
@Pipe({name: 'json'})
|
||||
@Injectable()
|
||||
export class JsonPipe implements PipeTransform {
|
||||
transform(value: any, args: List<any> = null): string { return Json.stringify(value); }
|
||||
}
|
81
modules/angular2/src/core/pipes/limit_to_pipe.ts
Normal file
81
modules/angular2/src/core/pipes/limit_to_pipe.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import {
|
||||
isBlank,
|
||||
isString,
|
||||
isArray,
|
||||
StringWrapper,
|
||||
BaseException,
|
||||
CONST
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {Math} from 'angular2/src/facade/math';
|
||||
import {Injectable} from 'angular2/di';
|
||||
|
||||
import {PipeTransform, WrappedValue} from 'angular2/change_detection';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
import {Pipe} from '../core/metadata';
|
||||
|
||||
/**
|
||||
* Creates a new List or String containing only a prefix/suffix of the
|
||||
* elements.
|
||||
*
|
||||
* The number of elements to return is specified by the `limitTo` parameter.
|
||||
*
|
||||
* # Usage
|
||||
*
|
||||
* expression | limitTo:number
|
||||
*
|
||||
* Where the input expression is a [List] or [String], and `limitTo` is:
|
||||
*
|
||||
* - **a positive integer**: return _number_ items from the beginning of the list or string
|
||||
* expression.
|
||||
* - **a negative integer**: return _number_ items from the end of the list or string expression.
|
||||
* - **`|limitTo|` greater than the size of the expression**: return the entire expression.
|
||||
*
|
||||
* When operating on a [List], the returned list is always a copy even when all
|
||||
* the elements are being returned.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ## List Example
|
||||
*
|
||||
* Assuming `var collection = ['a', 'b', 'c']`, this `ng-for` directive:
|
||||
*
|
||||
* <li *ng-for="var i in collection | limitTo:2">{{i}}</li>
|
||||
*
|
||||
* produces the following:
|
||||
*
|
||||
* <li>a</li>
|
||||
* <li>b</li>
|
||||
*
|
||||
* ## String Examples
|
||||
*
|
||||
* {{ 'abcdefghij' | limitTo: 4 }} // output is 'abcd'
|
||||
* {{ 'abcdefghij' | limitTo: -4 }} // output is 'ghij'
|
||||
* {{ 'abcdefghij' | limitTo: -100 }} // output is 'abcdefghij'
|
||||
*/
|
||||
@Pipe({name: 'limitTo'})
|
||||
@Injectable()
|
||||
export class LimitToPipe implements PipeTransform {
|
||||
supports(obj: any): boolean { return isString(obj) || isArray(obj); }
|
||||
|
||||
transform(value: any, args: List<any> = null): any {
|
||||
if (isBlank(args) || args.length == 0) {
|
||||
throw new BaseException('limitTo pipe requires one argument');
|
||||
}
|
||||
if (!this.supports(value)) {
|
||||
throw new InvalidPipeArgumentException(LimitToPipe, value);
|
||||
}
|
||||
if (isBlank(value)) return value;
|
||||
var limit: number = args[0];
|
||||
var left = 0, right = Math.min(limit, value.length);
|
||||
if (limit < 0) {
|
||||
left = Math.max(0, value.length + limit);
|
||||
right = value.length;
|
||||
}
|
||||
if (isString(value)) {
|
||||
return StringWrapper.substring(value, left, right);
|
||||
}
|
||||
return ListWrapper.slice(value, left, right);
|
||||
}
|
||||
}
|
41
modules/angular2/src/core/pipes/lowercase_pipe.ts
Normal file
41
modules/angular2/src/core/pipes/lowercase_pipe.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import {isString, StringWrapper, CONST, isBlank} from 'angular2/src/facade/lang';
|
||||
import {Injectable} from 'angular2/di';
|
||||
|
||||
import {PipeTransform, WrappedValue} from 'angular2/change_detection';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
|
||||
import {Pipe} from '../core/metadata';
|
||||
|
||||
/**
|
||||
* Implements lowercase transforms to text.
|
||||
*
|
||||
* # Example
|
||||
*
|
||||
* In this example we transform the user text lowercase.
|
||||
*
|
||||
* ```
|
||||
* @Component({
|
||||
* selector: "username-cmp"
|
||||
* })
|
||||
* @View({
|
||||
* template: "Username: {{ user | lowercase }}"
|
||||
* })
|
||||
* class Username {
|
||||
* user:string;
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
@CONST()
|
||||
@Pipe({name: 'lowercase'})
|
||||
@Injectable()
|
||||
export class LowerCasePipe implements PipeTransform {
|
||||
transform(value: string, args: List<any> = null): string {
|
||||
if (isBlank(value)) return value;
|
||||
if (!isString(value)) {
|
||||
throw new InvalidPipeArgumentException(LowerCasePipe, value);
|
||||
}
|
||||
return StringWrapper.toLowerCase(value);
|
||||
}
|
||||
}
|
146
modules/angular2/src/core/pipes/number_pipe.ts
Normal file
146
modules/angular2/src/core/pipes/number_pipe.ts
Normal file
@ -0,0 +1,146 @@
|
||||
import {
|
||||
isNumber,
|
||||
isPresent,
|
||||
isBlank,
|
||||
StringWrapper,
|
||||
NumberWrapper,
|
||||
RegExpWrapper,
|
||||
BaseException,
|
||||
CONST,
|
||||
FunctionWrapper
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {NumberFormatter, NumberFormatStyle} from 'angular2/src/facade/intl';
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {PipeTransform, WrappedValue} from 'angular2/change_detection';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
import {Pipe} from '../core/metadata';
|
||||
|
||||
var defaultLocale: string = 'en-US';
|
||||
var _re = RegExpWrapper.create('^(\\d+)?\\.((\\d+)(\\-(\\d+))?)?$');
|
||||
|
||||
@CONST()
|
||||
@Injectable()
|
||||
export class NumberPipe {
|
||||
static _format(value: number, style: NumberFormatStyle, digits: string, currency: string = null,
|
||||
currencyAsSymbol: boolean = false): string {
|
||||
if (isBlank(value)) return null;
|
||||
if (!isNumber(value)) {
|
||||
throw new InvalidPipeArgumentException(NumberPipe, value);
|
||||
}
|
||||
var minInt = 1, minFraction = 0, maxFraction = 3;
|
||||
if (isPresent(digits)) {
|
||||
var parts = RegExpWrapper.firstMatch(_re, digits);
|
||||
if (isBlank(parts)) {
|
||||
throw new BaseException(`${digits} is not a valid digit info for number pipes`);
|
||||
}
|
||||
if (isPresent(parts[1])) { // min integer digits
|
||||
minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
|
||||
}
|
||||
if (isPresent(parts[3])) { // min fraction digits
|
||||
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
|
||||
}
|
||||
if (isPresent(parts[5])) { // max fraction digits
|
||||
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
|
||||
}
|
||||
}
|
||||
return NumberFormatter.format(value, defaultLocale, style, {
|
||||
minimumIntegerDigits: minInt,
|
||||
minimumFractionDigits: minFraction,
|
||||
maximumFractionDigits: maxFraction,
|
||||
currency: currency,
|
||||
currencyAsSymbol: currencyAsSymbol
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: this pipe uses the Internationalization API.
|
||||
* Therefore it is only reliable in Chrome and Opera browsers.
|
||||
*
|
||||
* Formats a number as local text. i.e. group sizing and seperator and other locale-specific
|
||||
* configurations are based on the active locale.
|
||||
*
|
||||
* # Usage
|
||||
*
|
||||
* expression | number[:digitInfo]
|
||||
*
|
||||
* where `expression` is a number and `digitInfo` has the following format:
|
||||
*
|
||||
* {minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}
|
||||
*
|
||||
* - minIntegerDigits is the minimum number of integer digits to use. Defaults to 1.
|
||||
* - minFractionDigits is the minimum number of digits after fraction. Defaults to 0.
|
||||
* - maxFractionDigits is the maximum number of digits after fraction. Defaults to 3.
|
||||
*
|
||||
* For more information on the acceptable range for each of these numbers and other
|
||||
* details see your native internationalization library.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* {{ 123 | number }} // output is 123
|
||||
* {{ 123.1 | number: '.2-3' }} // output is 123.10
|
||||
* {{ 1 | number: '2.2' }} // output is 01.00
|
||||
*/
|
||||
@CONST()
|
||||
@Pipe({name: 'number'})
|
||||
@Injectable()
|
||||
export class DecimalPipe extends NumberPipe implements PipeTransform {
|
||||
transform(value: any, args: any[]): string {
|
||||
var digits: string = ListWrapper.first(args);
|
||||
return NumberPipe._format(value, NumberFormatStyle.DECIMAL, digits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: this pipe uses the Internationalization API.
|
||||
* Therefore it is only reliable in Chrome and Opera browsers.
|
||||
*
|
||||
* Formats a number as local percent.
|
||||
*
|
||||
* # Usage
|
||||
*
|
||||
* expression | percent[:digitInfo]
|
||||
*
|
||||
* For more information about `digitInfo` see {@link DecimalPipe}
|
||||
*/
|
||||
@CONST()
|
||||
@Pipe({name: 'percent'})
|
||||
@Injectable()
|
||||
export class PercentPipe extends NumberPipe implements PipeTransform {
|
||||
transform(value: any, args: any[]): string {
|
||||
var digits: string = ListWrapper.first(args);
|
||||
return NumberPipe._format(value, NumberFormatStyle.PERCENT, digits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: this pipe uses the Internationalization API.
|
||||
* Therefore it is only reliable in Chrome and Opera browsers.
|
||||
*
|
||||
* Formats a number as local currency.
|
||||
*
|
||||
* # Usage
|
||||
*
|
||||
* expression | currency[:currencyCode[:symbolDisplay[:digitInfo]]]
|
||||
*
|
||||
* where `currencyCode` is the ISO 4217 currency code, such as "USD" for the US dollar and
|
||||
* "EUR" for the euro. `symbolDisplay` is a boolean indicating whether to use the currency
|
||||
* symbol (e.g. $) or the currency code (e.g. USD) in the output. The default for this value
|
||||
* is `false`.
|
||||
* For more information about `digitInfo` see {@link DecimalPipe}
|
||||
*/
|
||||
@CONST()
|
||||
@Pipe({name: 'currency'})
|
||||
@Injectable()
|
||||
export class CurrencyPipe extends NumberPipe implements PipeTransform {
|
||||
transform(value: any, args: any[]): string {
|
||||
var currencyCode: string = isPresent(args) && args.length > 0 ? args[0] : 'USD';
|
||||
var symbolDisplay: boolean = isPresent(args) && args.length > 1 ? args[1] : false;
|
||||
var digits: string = isPresent(args) && args.length > 2 ? args[2] : null;
|
||||
return NumberPipe._format(value, NumberFormatStyle.CURRENCY, digits, currencyCode,
|
||||
symbolDisplay);
|
||||
}
|
||||
}
|
40
modules/angular2/src/core/pipes/uppercase_pipe.ts
Normal file
40
modules/angular2/src/core/pipes/uppercase_pipe.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import {isString, StringWrapper, CONST, isBlank} from 'angular2/src/facade/lang';
|
||||
import {Injectable} from 'angular2/di';
|
||||
|
||||
import {PipeTransform, WrappedValue} from 'angular2/change_detection';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
import {Pipe} from '../core/metadata';
|
||||
|
||||
/**
|
||||
* Implements uppercase transforms to text.
|
||||
*
|
||||
* # Example
|
||||
*
|
||||
* In this example we transform the user text uppercase.
|
||||
*
|
||||
* ```
|
||||
* @Component({
|
||||
* selector: "username-cmp"
|
||||
* })
|
||||
* @View({
|
||||
* template: "Username: {{ user | uppercase }}"
|
||||
* })
|
||||
* class Username {
|
||||
* user:string;
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
@CONST()
|
||||
@Pipe({name: 'uppercase'})
|
||||
@Injectable()
|
||||
export class UpperCasePipe implements PipeTransform {
|
||||
transform(value: string, args: List<any> = null): string {
|
||||
if (isBlank(value)) return value;
|
||||
if (!isString(value)) {
|
||||
throw new InvalidPipeArgumentException(UpperCasePipe, value);
|
||||
}
|
||||
return StringWrapper.toUpperCase(value);
|
||||
}
|
||||
}
|
80
modules/angular2/src/core/profile/profile.ts
Normal file
80
modules/angular2/src/core/profile/profile.ts
Normal file
@ -0,0 +1,80 @@
|
||||
export {WtfScopeFn} from './wtf_impl';
|
||||
|
||||
import * as impl from "./wtf_impl";
|
||||
|
||||
// Change exports to const once https://github.com/angular/ts2dart/issues/150
|
||||
|
||||
/**
|
||||
* True if WTF is enabled.
|
||||
*/
|
||||
export var wtfEnabled = impl.detectWTF();
|
||||
|
||||
function noopScope(arg0?: any, arg1?: any): any {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create trace scope.
|
||||
*
|
||||
* Scopes must be strictly nested and are analogous to stack frames, but
|
||||
* do not have to follow the stack frames. Instead it is recommended that they follow logical
|
||||
* nesting. You may want to use
|
||||
* [Event
|
||||
* Signatures](http://google.github.io/tracing-framework/instrumenting-code.html#custom-events)
|
||||
* as they are defined in WTF.
|
||||
*
|
||||
* Used to mark scope entry. The return value is used to leave the scope.
|
||||
*
|
||||
* final myScope = wtfCreateScope('MyClass#myMethod(ascii someVal)');
|
||||
*
|
||||
* someMethod() {
|
||||
* var s = myScope('Foo'); // 'Foo' gets stored in tracing UI
|
||||
* // DO SOME WORK HERE
|
||||
* return wtfLeave(s, 123); // Return value 123
|
||||
* }
|
||||
*
|
||||
* Note, adding try-finally block around the work to ensure that `wtfLeave` gets called can
|
||||
* negatively impact the performance of your application. For this reason we recommend that
|
||||
* you don't add them to ensure that `wtfLeave` gets called. In production `wtfLeave` is a noop and
|
||||
* so try-finally block has no value. When debugging perf issues, skipping `wtfLeave`, do to
|
||||
* exception, will produce incorrect trace, but presence of exception signifies logic error which
|
||||
* needs to be fixed before the app should be profiled. Add try-finally only when you expect that
|
||||
* an exception is expected during normal execution while profiling.
|
||||
*
|
||||
*/
|
||||
export var wtfCreateScope: (signature: string, flags?: any) => impl.WtfScopeFn =
|
||||
wtfEnabled ? impl.createScope : (signature: string, flags?: any) => noopScope;
|
||||
|
||||
/**
|
||||
* Used to mark end of Scope.
|
||||
*
|
||||
* - `scope` to end.
|
||||
* - `returnValue` (optional) to be passed to the WTF.
|
||||
*
|
||||
* Returns the `returnValue for easy chaining.
|
||||
*/
|
||||
export var wtfLeave:<T>(scope: any, returnValue?: T) => T =
|
||||
wtfEnabled ? impl.leave : (s: any, r?: any) => r;
|
||||
|
||||
/**
|
||||
* Used to mark Async start. Async are similar to scope but they don't have to be strictly nested.
|
||||
* The return value is used in the call to [endAsync]. Async ranges only work if WTF has been
|
||||
* enabled.
|
||||
*
|
||||
* someMethod() {
|
||||
* var s = wtfStartTimeRange('HTTP:GET', 'some.url');
|
||||
* var future = new Future.delay(5).then((_) {
|
||||
* wtfEndTimeRange(s);
|
||||
* });
|
||||
* }
|
||||
*/
|
||||
export var wtfStartTimeRange: (rangeType: string, action: string) => any =
|
||||
wtfEnabled ? impl.startTimeRange : (rangeType: string, action: string) => null;
|
||||
|
||||
/**
|
||||
* Ends a async time range operation.
|
||||
* [range] is the return value from [wtfStartTimeRange] Async ranges only work if WTF has been
|
||||
* enabled.
|
||||
*/
|
||||
export var wtfEndTimeRange: (range: any) => void = wtfEnabled ? impl.endTimeRange : (r: any) =>
|
||||
null;
|
96
modules/angular2/src/core/profile/wtf_impl.dart
Normal file
96
modules/angular2/src/core/profile/wtf_impl.dart
Normal file
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Tracing for Dart applications.
|
||||
*
|
||||
* The tracing API hooks up to either [WTF](http://google.github.io/tracing-framework/) or
|
||||
* [Dart Observatory](https://www.dartlang.org/tools/observatory/).
|
||||
*/
|
||||
library angular2.src.core.wtf_impl;
|
||||
|
||||
typedef dynamic WtfScopeFn([arg0, arg1]);
|
||||
|
||||
var context = null;
|
||||
var _trace;
|
||||
var _events;
|
||||
var _createScope;
|
||||
var _leaveScope;
|
||||
var _beginTimeRange;
|
||||
var _endTimeRange;
|
||||
final List _arg1 = [null];
|
||||
final List _arg2 = [null, null];
|
||||
|
||||
bool detectWTF() {
|
||||
if (context != null && context.hasProperty('wtf')) {
|
||||
var wtf = context['wtf'];
|
||||
if (wtf.hasProperty('trace')) {
|
||||
_trace = wtf['trace'];
|
||||
_events = _trace['events'];
|
||||
_createScope = _events['createScope'];
|
||||
_leaveScope = _trace['leaveScope'];
|
||||
_beginTimeRange = _trace['beginTimeRange'];
|
||||
_endTimeRange = _trace['endTimeRange'];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int getArgSize(String signature) {
|
||||
int start = signature.indexOf('(') + 1;
|
||||
int end = signature.indexOf(')', start);
|
||||
bool found = false;
|
||||
int count = 0;
|
||||
for (var i = start; i < end; i++) {
|
||||
var ch = signature[i];
|
||||
if (identical(ch, ',')) {
|
||||
found = false;
|
||||
}
|
||||
if (!found) {
|
||||
found = true;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
dynamic createScope(String signature, [flags]) {
|
||||
_arg2[0] = signature;
|
||||
_arg2[1] = flags;
|
||||
var jsScope = _createScope.apply(_arg2, thisArg: _events);
|
||||
switch (getArgSize(signature)) {
|
||||
case 0:
|
||||
return ([arg0, arg1]) {
|
||||
return jsScope.apply(const []);
|
||||
};
|
||||
case 1:
|
||||
return ([arg0, arg1]) {
|
||||
_arg1[0] = arg0;
|
||||
return jsScope.apply(_arg1);
|
||||
};
|
||||
case 2:
|
||||
return ([arg0, arg1]) {
|
||||
_arg2[0] = arg0;
|
||||
_arg2[1] = arg1;
|
||||
return jsScope.apply(_arg2);
|
||||
};
|
||||
default:
|
||||
throw "Max 2 arguments are supported.";
|
||||
}
|
||||
}
|
||||
|
||||
void leave(scope, [returnValue]) {
|
||||
_arg2[0] = scope;
|
||||
_arg2[1] = returnValue;
|
||||
_leaveScope.apply(_arg2, thisArg: _trace);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
dynamic startTimeRange(String rangeType, String action) {
|
||||
_arg2[0] = rangeType;
|
||||
_arg2[1] = action;
|
||||
return _beginTimeRange.apply(_arg2, thisArg: _trace);
|
||||
}
|
||||
|
||||
void endTimeRange(dynamic range) {
|
||||
_arg1[0] = range;
|
||||
_endTimeRange.apply(_arg1, thisArg: _trace);
|
||||
}
|
56
modules/angular2/src/core/profile/wtf_impl.ts
Normal file
56
modules/angular2/src/core/profile/wtf_impl.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import {global} from '../facade/lang';
|
||||
|
||||
export interface WtfScopeFn { (arg0?: any, arg1?: any): any; }
|
||||
|
||||
interface WTF {
|
||||
trace: Trace;
|
||||
}
|
||||
|
||||
interface Trace {
|
||||
events: Events;
|
||||
leaveScope(scope: Scope, returnValue: any);
|
||||
beginTimeRange(rangeType: string, action: string): Range;
|
||||
endTimeRange(range: Range);
|
||||
}
|
||||
|
||||
interface Range {}
|
||||
|
||||
interface Events {
|
||||
createScope(signature: string, flags: any): Scope;
|
||||
}
|
||||
|
||||
interface Scope {
|
||||
(...args): any;
|
||||
}
|
||||
|
||||
var trace: Trace;
|
||||
var events: Events;
|
||||
|
||||
export function detectWTF(): boolean {
|
||||
var wtf: WTF = global['wtf'];
|
||||
if (wtf) {
|
||||
trace = wtf['trace'];
|
||||
if (trace) {
|
||||
events = trace['events'];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function createScope(signature: string, flags: any = null): any {
|
||||
return events.createScope(signature, flags);
|
||||
}
|
||||
|
||||
export function leave<T>(scope: Scope, returnValue?: T): T {
|
||||
trace.leaveScope(scope, returnValue);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
export function startTimeRange(rangeType: string, action: string): Range {
|
||||
return trace.beginTimeRange(rangeType, action);
|
||||
}
|
||||
|
||||
export function endTimeRange(range: Range): void {
|
||||
trace.endTimeRange(range);
|
||||
}
|
14
modules/angular2/src/core/profile/wtf_init.dart
Normal file
14
modules/angular2/src/core/profile/wtf_init.dart
Normal file
@ -0,0 +1,14 @@
|
||||
library angular2.src.core.wtf_init;
|
||||
|
||||
import 'dart:js' as js;
|
||||
import 'wtf_impl.dart' as impl;
|
||||
|
||||
/**
|
||||
* Must be executed explicitly in Dart to set the JS Context.
|
||||
*
|
||||
* NOTE: this is done explicitly to allow WTF api not to depend on
|
||||
* JS context and possible to run the noop WTF stubs outside the browser.
|
||||
*/
|
||||
wtfInit() {
|
||||
impl.context = js.context;
|
||||
}
|
4
modules/angular2/src/core/profile/wtf_init.ts
Normal file
4
modules/angular2/src/core/profile/wtf_init.ts
Normal file
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* This is here because DART requires it. It is noop in JS.
|
||||
*/
|
||||
export function wtfInit() {}
|
@ -0,0 +1,58 @@
|
||||
library reflection.debug_reflection_capabilities;
|
||||
|
||||
import 'dart:mirrors';
|
||||
import 'package:logging/logging.dart' as log;
|
||||
import 'package:stack_trace/stack_trace.dart';
|
||||
import 'types.dart';
|
||||
import 'reflection_capabilities.dart' as standard;
|
||||
|
||||
class ReflectionCapabilities extends standard.ReflectionCapabilities {
|
||||
final bool _verbose;
|
||||
final log.Logger _log = new log.Logger('ReflectionCapabilities');
|
||||
|
||||
ReflectionCapabilities({bool verbose: false})
|
||||
: _verbose = verbose,
|
||||
super() {
|
||||
log.hierarchicalLoggingEnabled = true;
|
||||
_log.level = _verbose ? log.Level.ALL : log.Level.INFO;
|
||||
_log.onRecord.listen((log.LogRecord rec) {
|
||||
print('[${rec.loggerName}(${rec.level.name})]: ${rec.message}');
|
||||
});
|
||||
}
|
||||
|
||||
void _notify(String methodName, param) {
|
||||
var trace = _verbose ? ' ${Trace.format(new Trace.current())}' : '';
|
||||
_log.info('"$methodName" requested for "$param".$trace');
|
||||
}
|
||||
|
||||
Function factory(Type type) {
|
||||
ClassMirror classMirror = reflectType(type);
|
||||
_notify('factory', '${classMirror.qualifiedName}');
|
||||
return super.factory(type);
|
||||
}
|
||||
|
||||
List<List> parameters(typeOrFunc) {
|
||||
_notify('parameters', typeOrFunc);
|
||||
return super.parameters(typeOrFunc);
|
||||
}
|
||||
|
||||
List annotations(typeOrFunc) {
|
||||
_notify('annotations', typeOrFunc);
|
||||
return super.annotations(typeOrFunc);
|
||||
}
|
||||
|
||||
GetterFn getter(String name) {
|
||||
_notify('getter', name);
|
||||
return super.getter(name);
|
||||
}
|
||||
|
||||
SetterFn setter(String name) {
|
||||
_notify('setter', name);
|
||||
return super.setter(name);
|
||||
}
|
||||
|
||||
MethodFn method(String name) {
|
||||
_notify('method', name);
|
||||
return super.method(name);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import {Type} from 'angular2/src/facade/lang';
|
||||
import {GetterFn, SetterFn, MethodFn} from './types';
|
||||
import {List} from 'angular2/src/facade/collection';
|
||||
|
||||
export interface PlatformReflectionCapabilities {
|
||||
isReflectionEnabled(): boolean;
|
||||
factory(type: Type): Function;
|
||||
interfaces(type: Type): List<any>;
|
||||
parameters(type: Type): List<List<any>>;
|
||||
annotations(type: Type): List<any>;
|
||||
getter(name: string): GetterFn;
|
||||
setter(name: string): SetterFn;
|
||||
method(name: string): MethodFn;
|
||||
}
|
43
modules/angular2/src/core/reflection/reflection.dart
Normal file
43
modules/angular2/src/core/reflection/reflection.dart
Normal file
@ -0,0 +1,43 @@
|
||||
library reflection.reflection;
|
||||
|
||||
import 'reflector.dart';
|
||||
import 'types.dart';
|
||||
export 'reflector.dart';
|
||||
import 'platform_reflection_capabilities.dart';
|
||||
import 'package:angular2/src/facade/lang.dart';
|
||||
|
||||
class NoReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
bool isReflectionEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
Function factory(Type type) {
|
||||
throw "Cannot find reflection information on ${stringify(type)}";
|
||||
}
|
||||
|
||||
List interfaces(Type type) {
|
||||
throw "Cannot find reflection information on ${stringify(type)}";
|
||||
}
|
||||
|
||||
List parameters(Type type) {
|
||||
throw "Cannot find reflection information on ${stringify(type)}";
|
||||
}
|
||||
|
||||
List annotations(Type type) {
|
||||
throw "Cannot find reflection information on ${stringify(type)}";
|
||||
}
|
||||
|
||||
GetterFn getter(String name) {
|
||||
throw "Cannot find getter ${name}";
|
||||
}
|
||||
|
||||
SetterFn setter(String name) {
|
||||
throw "Cannot find setter ${name}";
|
||||
}
|
||||
|
||||
MethodFn method(String name) {
|
||||
throw "Cannot find method ${name}";
|
||||
}
|
||||
}
|
||||
|
||||
final Reflector reflector = new Reflector(new NoReflectionCapabilities());
|
7
modules/angular2/src/core/reflection/reflection.ts
Normal file
7
modules/angular2/src/core/reflection/reflection.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import {Type, isPresent} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {Reflector} from './reflector';
|
||||
export {Reflector, ReflectionInfo} from './reflector';
|
||||
import {ReflectionCapabilities} from './reflection_capabilities';
|
||||
|
||||
export var reflector = new Reflector(new ReflectionCapabilities());
|
@ -0,0 +1,300 @@
|
||||
library reflection.reflection_capabilities;
|
||||
|
||||
import 'package:angular2/src/facade/lang.dart';
|
||||
import 'types.dart';
|
||||
import 'dart:mirrors';
|
||||
import 'platform_reflection_capabilities.dart';
|
||||
|
||||
class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
ReflectionCapabilities([metadataReader]) {}
|
||||
|
||||
bool isReflectionEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
Function factory(Type type) {
|
||||
ClassMirror classMirror = reflectType(type);
|
||||
MethodMirror ctor = classMirror.declarations[classMirror.simpleName];
|
||||
Function create = classMirror.newInstance;
|
||||
Symbol name = ctor.constructorName;
|
||||
int length = ctor.parameters.length;
|
||||
|
||||
switch (length) {
|
||||
case 0:
|
||||
return () => create(name, []).reflectee;
|
||||
case 1:
|
||||
return (a1) => create(name, [a1]).reflectee;
|
||||
case 2:
|
||||
return (a1, a2) => create(name, [a1, a2]).reflectee;
|
||||
case 3:
|
||||
return (a1, a2, a3) => create(name, [a1, a2, a3]).reflectee;
|
||||
case 4:
|
||||
return (a1, a2, a3, a4) => create(name, [a1, a2, a3, a4]).reflectee;
|
||||
case 5:
|
||||
return (a1, a2, a3, a4, a5) =>
|
||||
create(name, [a1, a2, a3, a4, a5]).reflectee;
|
||||
case 6:
|
||||
return (a1, a2, a3, a4, a5, a6) =>
|
||||
create(name, [a1, a2, a3, a4, a5, a6]).reflectee;
|
||||
case 7:
|
||||
return (a1, a2, a3, a4, a5, a6, a7) =>
|
||||
create(name, [a1, a2, a3, a4, a5, a6, a7]).reflectee;
|
||||
case 8:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8) =>
|
||||
create(name, [a1, a2, a3, a4, a5, a6, a7, a8]).reflectee;
|
||||
case 9:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9) =>
|
||||
create(name, [a1, a2, a3, a4, a5, a6, a7, a8, a9]).reflectee;
|
||||
case 10:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) =>
|
||||
create(name, [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10]).reflectee;
|
||||
case 11:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) =>
|
||||
create(name, [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11])
|
||||
.reflectee;
|
||||
case 12:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) =>
|
||||
create(name, [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12])
|
||||
.reflectee;
|
||||
case 13:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) =>
|
||||
create(name, [
|
||||
a1,
|
||||
a2,
|
||||
a3,
|
||||
a4,
|
||||
a5,
|
||||
a6,
|
||||
a7,
|
||||
a8,
|
||||
a9,
|
||||
a10,
|
||||
a11,
|
||||
a12,
|
||||
a13
|
||||
]).reflectee;
|
||||
case 14:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) =>
|
||||
create(name, [
|
||||
a1,
|
||||
a2,
|
||||
a3,
|
||||
a4,
|
||||
a5,
|
||||
a6,
|
||||
a7,
|
||||
a8,
|
||||
a9,
|
||||
a10,
|
||||
a11,
|
||||
a12,
|
||||
a13,
|
||||
a14
|
||||
]).reflectee;
|
||||
case 15:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14,
|
||||
a15) =>
|
||||
create(name, [
|
||||
a1,
|
||||
a2,
|
||||
a3,
|
||||
a4,
|
||||
a5,
|
||||
a6,
|
||||
a7,
|
||||
a8,
|
||||
a9,
|
||||
a10,
|
||||
a11,
|
||||
a12,
|
||||
a13,
|
||||
a14,
|
||||
a15
|
||||
]).reflectee;
|
||||
case 16:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14,
|
||||
a15, a16) =>
|
||||
create(name, [
|
||||
a1,
|
||||
a2,
|
||||
a3,
|
||||
a4,
|
||||
a5,
|
||||
a6,
|
||||
a7,
|
||||
a8,
|
||||
a9,
|
||||
a10,
|
||||
a11,
|
||||
a12,
|
||||
a13,
|
||||
a14,
|
||||
a15,
|
||||
a16
|
||||
]).reflectee;
|
||||
case 17:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14,
|
||||
a15, a16, a17) =>
|
||||
create(name, [
|
||||
a1,
|
||||
a2,
|
||||
a3,
|
||||
a4,
|
||||
a5,
|
||||
a6,
|
||||
a7,
|
||||
a8,
|
||||
a9,
|
||||
a10,
|
||||
a11,
|
||||
a12,
|
||||
a13,
|
||||
a14,
|
||||
a15,
|
||||
a16,
|
||||
a17
|
||||
]).reflectee;
|
||||
case 18:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14,
|
||||
a15, a16, a17, a18) =>
|
||||
create(name, [
|
||||
a1,
|
||||
a2,
|
||||
a3,
|
||||
a4,
|
||||
a5,
|
||||
a6,
|
||||
a7,
|
||||
a8,
|
||||
a9,
|
||||
a10,
|
||||
a11,
|
||||
a12,
|
||||
a13,
|
||||
a14,
|
||||
a15,
|
||||
a16,
|
||||
a17,
|
||||
a18
|
||||
]).reflectee;
|
||||
case 19:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14,
|
||||
a15, a16, a17, a18, a19) =>
|
||||
create(name, [
|
||||
a1,
|
||||
a2,
|
||||
a3,
|
||||
a4,
|
||||
a5,
|
||||
a6,
|
||||
a7,
|
||||
a8,
|
||||
a9,
|
||||
a10,
|
||||
a11,
|
||||
a12,
|
||||
a13,
|
||||
a14,
|
||||
a15,
|
||||
a16,
|
||||
a17,
|
||||
a18,
|
||||
a19
|
||||
]).reflectee;
|
||||
case 20:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14,
|
||||
a15, a16, a17, a18, a19, a20) =>
|
||||
create(name, [
|
||||
a1,
|
||||
a2,
|
||||
a3,
|
||||
a4,
|
||||
a5,
|
||||
a6,
|
||||
a7,
|
||||
a8,
|
||||
a9,
|
||||
a10,
|
||||
a11,
|
||||
a12,
|
||||
a13,
|
||||
a14,
|
||||
a15,
|
||||
a16,
|
||||
a17,
|
||||
a18,
|
||||
a19,
|
||||
a20
|
||||
]).reflectee;
|
||||
}
|
||||
|
||||
throw "Cannot create a factory for '${stringify(type)}' because its constructor has more than 20 arguments";
|
||||
}
|
||||
|
||||
List<List> parameters(typeOrFunc) {
|
||||
final parameters = typeOrFunc is Type
|
||||
? _constructorParameters(typeOrFunc)
|
||||
: _functionParameters(typeOrFunc);
|
||||
return parameters.map(_convertParameter).toList();
|
||||
}
|
||||
|
||||
List _convertParameter(ParameterMirror p) {
|
||||
var t = p.type;
|
||||
var res = (!t.hasReflectedType || t.reflectedType == dynamic)
|
||||
? []
|
||||
: [t.reflectedType];
|
||||
res.addAll(p.metadata.map((m) => m.reflectee));
|
||||
return res;
|
||||
}
|
||||
|
||||
List annotations(typeOrFunc) {
|
||||
final meta = typeOrFunc is Type
|
||||
? _constructorMetadata(typeOrFunc)
|
||||
: _functionMetadata(typeOrFunc);
|
||||
|
||||
return meta.map((m) => m.reflectee).toList();
|
||||
}
|
||||
|
||||
List interfaces(type) {
|
||||
ClassMirror classMirror = reflectType(type);
|
||||
return classMirror.superinterfaces.map((si) => si.reflectedType).toList();
|
||||
}
|
||||
|
||||
GetterFn getter(String name) {
|
||||
var symbol = new Symbol(name);
|
||||
return (receiver) => reflect(receiver).getField(symbol).reflectee;
|
||||
}
|
||||
|
||||
SetterFn setter(String name) {
|
||||
var symbol = new Symbol(name);
|
||||
return (receiver, value) =>
|
||||
reflect(receiver).setField(symbol, value).reflectee;
|
||||
}
|
||||
|
||||
MethodFn method(String name) {
|
||||
var symbol = new Symbol(name);
|
||||
return (receiver, posArgs) =>
|
||||
reflect(receiver).invoke(symbol, posArgs).reflectee;
|
||||
}
|
||||
|
||||
List _functionParameters(Function func) {
|
||||
var closureMirror = reflect(func);
|
||||
return closureMirror.function.parameters;
|
||||
}
|
||||
|
||||
List _constructorParameters(Type type) {
|
||||
ClassMirror classMirror = reflectType(type);
|
||||
MethodMirror ctor = classMirror.declarations[classMirror.simpleName];
|
||||
return ctor.parameters;
|
||||
}
|
||||
|
||||
List _functionMetadata(Function func) {
|
||||
var closureMirror = reflect(func);
|
||||
return closureMirror.function.metadata;
|
||||
}
|
||||
|
||||
List _constructorMetadata(Type type) {
|
||||
ClassMirror classMirror = reflectType(type);
|
||||
return classMirror.metadata;
|
||||
}
|
||||
}
|
158
modules/angular2/src/core/reflection/reflection_capabilities.ts
Normal file
158
modules/angular2/src/core/reflection/reflection_capabilities.ts
Normal file
@ -0,0 +1,158 @@
|
||||
import {
|
||||
Type,
|
||||
isPresent,
|
||||
isFunction,
|
||||
global,
|
||||
stringify,
|
||||
BaseException
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {GetterFn, SetterFn, MethodFn} from './types';
|
||||
import {PlatformReflectionCapabilities} from 'platform_reflection_capabilities';
|
||||
|
||||
export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
private _reflect: any;
|
||||
|
||||
constructor(reflect?: any) { this._reflect = isPresent(reflect) ? reflect : global.Reflect; }
|
||||
|
||||
isReflectionEnabled(): boolean { return true; }
|
||||
|
||||
factory(t: Type): Function {
|
||||
switch (t.length) {
|
||||
case 0:
|
||||
return () => new t();
|
||||
case 1:
|
||||
return (a1) => new t(a1);
|
||||
case 2:
|
||||
return (a1, a2) => new t(a1, a2);
|
||||
case 3:
|
||||
return (a1, a2, a3) => new t(a1, a2, a3);
|
||||
case 4:
|
||||
return (a1, a2, a3, a4) => new t(a1, a2, a3, a4);
|
||||
case 5:
|
||||
return (a1, a2, a3, a4, a5) => new t(a1, a2, a3, a4, a5);
|
||||
case 6:
|
||||
return (a1, a2, a3, a4, a5, a6) => new t(a1, a2, a3, a4, a5, a6);
|
||||
case 7:
|
||||
return (a1, a2, a3, a4, a5, a6, a7) => new t(a1, a2, a3, a4, a5, a6, a7);
|
||||
case 8:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8) => new t(a1, a2, a3, a4, a5, a6, a7, a8);
|
||||
case 9:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => new t(a1, a2, a3, a4, a5, a6, a7, a8, a9);
|
||||
case 10:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) =>
|
||||
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
|
||||
case 11:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) =>
|
||||
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11);
|
||||
case 12:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) =>
|
||||
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);
|
||||
case 13:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) =>
|
||||
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13);
|
||||
case 14:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) =>
|
||||
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14);
|
||||
case 15:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) =>
|
||||
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15);
|
||||
case 16:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) =>
|
||||
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16);
|
||||
case 17:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17) =>
|
||||
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16,
|
||||
a17);
|
||||
case 18:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18) =>
|
||||
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17,
|
||||
a18);
|
||||
case 19:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18,
|
||||
a19) => new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16,
|
||||
a17, a18, a19);
|
||||
case 20:
|
||||
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18,
|
||||
a19, a20) => new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15,
|
||||
a16, a17, a18, a19, a20);
|
||||
};
|
||||
|
||||
throw new Error(
|
||||
`Cannot create a factory for '${stringify(t)}' because its constructor has more than 20 arguments`);
|
||||
}
|
||||
|
||||
_zipTypesAndAnnotaions(paramTypes, paramAnnotations): List<List<any>> {
|
||||
var result;
|
||||
|
||||
if (typeof paramTypes === 'undefined') {
|
||||
result = ListWrapper.createFixedSize(paramAnnotations.length);
|
||||
} else {
|
||||
result = ListWrapper.createFixedSize(paramTypes.length);
|
||||
}
|
||||
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
// TS outputs Object for parameters without types, while Traceur omits
|
||||
// the annotations. For now we preserve the Traceur behavior to aid
|
||||
// migration, but this can be revisited.
|
||||
if (typeof paramTypes === 'undefined') {
|
||||
result[i] = [];
|
||||
} else if (paramTypes[i] != Object) {
|
||||
result[i] = [paramTypes[i]];
|
||||
} else {
|
||||
result[i] = [];
|
||||
}
|
||||
if (isPresent(paramAnnotations) && isPresent(paramAnnotations[i])) {
|
||||
result[i] = result[i].concat(paramAnnotations[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parameters(typeOfFunc: Type): List<List<any>> {
|
||||
// Prefer the direct API.
|
||||
if (isPresent((<any>typeOfFunc).parameters)) {
|
||||
return (<any>typeOfFunc).parameters;
|
||||
}
|
||||
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
|
||||
var paramAnnotations = this._reflect.getMetadata('parameters', typeOfFunc);
|
||||
var paramTypes = this._reflect.getMetadata('design:paramtypes', typeOfFunc);
|
||||
if (isPresent(paramTypes) || isPresent(paramAnnotations)) {
|
||||
return this._zipTypesAndAnnotaions(paramTypes, paramAnnotations);
|
||||
}
|
||||
}
|
||||
return ListWrapper.createFixedSize((<any>typeOfFunc).length);
|
||||
}
|
||||
|
||||
annotations(typeOfFunc: Type): List<any> {
|
||||
// Prefer the direct API.
|
||||
if (isPresent((<any>typeOfFunc).annotations)) {
|
||||
var annotations = (<any>typeOfFunc).annotations;
|
||||
if (isFunction(annotations) && annotations.annotations) {
|
||||
annotations = annotations.annotations;
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
|
||||
var annotations = this._reflect.getMetadata('annotations', typeOfFunc);
|
||||
if (isPresent(annotations)) return annotations;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
interfaces(type: Type): List<any> {
|
||||
throw new BaseException("JavaScript does not support interfaces");
|
||||
}
|
||||
|
||||
getter(name: string): GetterFn { return <GetterFn>new Function('o', 'return o.' + name + ';'); }
|
||||
|
||||
setter(name: string): SetterFn {
|
||||
return <SetterFn>new Function('o', 'v', 'return o.' + name + ' = v;');
|
||||
}
|
||||
|
||||
method(name: string): MethodFn {
|
||||
let functionBody = `if (!o.${name}) throw new Error('"${name}" is undefined');
|
||||
return o.${name}.apply(o, args);`;
|
||||
return <MethodFn>new Function('o', 'args', functionBody);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user